diff --git a/.github/scripts/install-mrdocs-package.sh b/.github/scripts/install-mrdocs-package.sh index 269df626b13..413c56c8bfd 100755 --- a/.github/scripts/install-mrdocs-package.sh +++ b/.github/scripts/install-mrdocs-package.sh @@ -49,6 +49,12 @@ echo "::endgroup::" echo "::group::Export environment variables" echo "MRDOCS_ROOT=$MRDOCS_INSTALL_DIR" echo "MRDOCS_ROOT=$MRDOCS_INSTALL_DIR" >> "$GITHUB_ENV" +# CMake-valid absolute prefix for find_package(mrdocs). MRDOCS_ROOT above is the +# Git Bash /d/... form on Windows, which CMake rejects; derive this one from +# GITHUB_WORKSPACE and normalize backslashes instead. +MRDOCS_PREFIX="${GITHUB_WORKSPACE//\\//}/.install/mrdocs" +echo "MRDOCS_PREFIX=$MRDOCS_PREFIX" +echo "MRDOCS_PREFIX=$MRDOCS_PREFIX" >> "$GITHUB_ENV" echo "PATH += $MRDOCS_INSTALL_DIR/bin" echo "$MRDOCS_INSTALL_DIR/bin" >> "$GITHUB_PATH" echo "::endgroup::" diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 2d72aa4ab68..98fd4776d9d 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -79,6 +79,8 @@ jobs: ${{ matrix.bootstrap-sanitizer && format('--sanitizer {0}', matrix.bootstrap-sanitizer) || '' }} \ ${{ matrix.common-ccflags && format('--cflags="{0}" --cxxflags="{0}"', matrix.common-ccflags) || '' }} cat "$RUNNER_TEMP/bootstrap-env.txt" >> "$GITHUB_ENV" + # mrdocs install prefix, reused for the install and the consumer test + echo "MRDOCS_PREFIX=${GITHUB_WORKSPACE//\\//}/.local" >> "$GITHUB_ENV" # Save the LLVM cache when bootstrap rebuilt it (cache miss or # stale stamp). BOOTSTRAP_REBUILT lists rebuilt recipes. @@ -129,7 +131,7 @@ jobs: generator: Ninja build-dir: build/mrdocs build-type: ${{ matrix.build-type }} - install-prefix: .local + install-prefix: ${{ env.MRDOCS_PREFIX }} export-compile-commands: true run-tests: true install: true @@ -139,6 +141,58 @@ jobs: package-artifact: false ctest-timeout: 9000 + # Consume the freshly installed mrdocs through its exported CMake package, + # from an out-of-tree project (tests/cmake): find_package(mrdocs), link + # mrdocs::mrdocs-core, and run the installed binary via MRDOCS_EXECUTABLE. + # This is the only coverage of mrdocs-as-a-library, so it runs on every + # normal build, on every platform (the default static library bundles its + # private toolchain, so this is where we prove a consumer links it with no + # LLVM/Clang/JerryScript/Lua present). It goes through the same + # cmake-workflow action as the main build (configure + build + ctest), + # pointed at tests/cmake, discovering the install through mrdocs_ROOT -- the + # package-specific find_package hint rather than CMAKE_PREFIX_PATH, so the + # _ROOT search path is exercised. Only the ABI flags mrdocs was + # built with are reused (the consumer must match the same standard library), + # not mrdocs's own -Werror/-static/-WX matrix flags. Coverage/sanitizer + # builds use instrumented toolchains an out-of-tree consumer would have to + # mirror exactly, so those are skipped. mrdocs_ROOT gets the MRDOCS_PREFIX + # resolved above. + - name: CMake Consumer Test (cmake test) + if: ${{ !matrix.coverage && !matrix.bootstrap-sanitizer }} + uses: alandefreitas/cpp-actions/cmake-workflow@v1.9.5 + with: + cmake-version: '>=3.26' + source-dir: tests/cmake + build-dir: build/cmake-consumer + cxxstd: ${{ matrix.cxxstd }} + cc: ${{ steps.setup-cpp.outputs.cc || matrix.cc }} + cxx: ${{ steps.setup-cpp.outputs.cxx || matrix.cxx }} + cxxflags: ${{ env.BOOTSTRAP_CXXFLAGS }} + ldflags: ${{ env.BOOTSTRAP_LDFLAGS }} + generator: Ninja + build-type: ${{ matrix.build-type }} + extra-args: -D mrdocs_ROOT=${{ env.MRDOCS_PREFIX }} + run-tests: true + install: false + + - name: CMake Consumer Test (library example) + if: ${{ !matrix.coverage && !matrix.bootstrap-sanitizer }} + uses: alandefreitas/cpp-actions/cmake-workflow@v1.9.5 + with: + cmake-version: '>=3.26' + source-dir: examples/library/breaking-changes + build-dir: build/example-breaking-changes + cxxstd: ${{ matrix.cxxstd }} + cc: ${{ steps.setup-cpp.outputs.cc || matrix.cc }} + cxx: ${{ steps.setup-cpp.outputs.cxx || matrix.cxx }} + cxxflags: ${{ env.BOOTSTRAP_CXXFLAGS }} + ldflags: ${{ env.BOOTSTRAP_LDFLAGS }} + generator: Ninja + build-type: ${{ matrix.build-type }} + extra-args: -D mrdocs_ROOT=${{ env.MRDOCS_PREFIX }} + run-tests: true + install: false + # Upload packages for ci-releases.yml to pick up - name: Upload GitHub Release Artifacts if: matrix.is-release-build == 'true' diff --git a/.github/workflows/ci-releases.yml b/.github/workflows/ci-releases.yml index ab349220bb6..001f5daa868 100644 --- a/.github/workflows/ci-releases.yml +++ b/.github/workflows/ci-releases.yml @@ -49,7 +49,7 @@ jobs: - name: Install packages uses: alandefreitas/cpp-actions/package-install@v1.9.5 with: - apt-get: build-essential asciidoctor cmake bzip2 git rsync + apt-get: build-essential asciidoctor cmake ninja-build bzip2 git rsync - name: Clone MrDocs uses: actions/checkout@v6 @@ -101,6 +101,24 @@ jobs: - name: Install MrDocs from Package run: .github/scripts/install-mrdocs-package.sh + # Discovers the package through mrdocs_ROOT (the package-specific + # find_package hint) rather than CMAKE_PREFIX_PATH. install-mrdocs-package.sh + # exports MRDOCS_PREFIX as its CMake-valid form. + - name: CMake Consumer Test (release package) + uses: alandefreitas/cpp-actions/cmake-workflow@v1.9.5 + with: + cmake-version: '>=3.26' + source-dir: tests/cmake + build-dir: build/cmake-consumer + cxxstd: ${{ matrix.cxxstd }} + cc: ${{ steps.setup-cpp.outputs.cc || matrix.cc }} + cxx: ${{ steps.setup-cpp.outputs.cxx || matrix.cxx }} + generator: Ninja + build-type: ${{ matrix.build-type }} + extra-args: -D mrdocs_ROOT=${{ env.MRDOCS_PREFIX }} + run-tests: true + install: false + - name: Clone Boost.URL uses: alandefreitas/cpp-actions/boost-clone@v1.9.5 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d87470fb67..3f323618685 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,34 +32,49 @@ include(utils/cmake/helpers.cmake) # Project options # #------------------------------------------------- -option(MRDOCS_INSTALL "Configure install target" ON) -option(MRDOCS_PACKAGE "Build install package" ON) -option(MRDOCS_BUILD_SHARED "Link shared" ${BUILD_SHARED_LIBS}) +# What to build +option(MRDOCS_MRDOCS_BUILD "Build only public-headers for documentation self-reference" OFF) +if (MRDOCS_MRDOCS_BUILD) + include(docs/mrdocs-build.cmake) + return() +endif() option(MRDOCS_BUILD_TESTS "Build tests" ${BUILD_TESTING}) +option(MRDOCS_BUILD_STRICT_TESTS "Enable costly strict tests" ON) option(MRDOCS_BUILD_EXAMPLES "Build the examples" ${MRDOCS_BUILD_TESTS}) +option(MRDOCS_BUILD_DOCS "Build documentation" OFF) +option(MRDOCS_GENERATE_REFERENCE "Generate MrDocs reference" ${MRDOCS_BUILD_DOCS}) +option(MRDOCS_INSTALL "Configure install target" ON) +option(MRDOCS_PACKAGE "Build install package" ON) if (MRDOCS_BUILD_TESTS OR MRDOCS_BUILD_EXAMPLES) enable_testing() include(CTest) endif() -option(MRDOCS_BUILD_STRICT_TESTS "Enable strict tests" ON) + +# How to build option(MRDOCS_REQUIRE_GIT "Git is required: not being able to extract version build is an error" ON) -option(MRDOCS_BUILD_DOCS "Build documentation" OFF) -option(MRDOCS_BUILD_HEADERS_ONLY "Build only public-headers for self-reference" OFF) -option(MRDOCS_GENERATE_REFERENCE "Generate MrDocs reference" ${MRDOCS_BUILD_DOCS}) +option(MRDOCS_BUNDLE_DEPENDENCIES "Bundle the private dependencies into the library" ON) +option(MRDOCS_BUILD_SHARED "Link shared" ${BUILD_SHARED_LIBS}) option(MRDOCS_GENERATE_ANTORA_REFERENCE "Generate MrDocs reference in Antora module pages" OFF) +# Helper variables based on options set_ternary(MRDOCS_LINK_MODE MRDOCS_BUILD_SHARED SHARED "") set_ternary(MRDOCS_LINK_MODE_DEFINITION MRDOCS_BUILD_SHARED MRDOCS_SHARED_LINK MRDOCS_STATIC_LINK) +set_ternary(MRDOCS_DO_BUNDLE_DEPENDENCIES "MRDOCS_BUNDLE_DEPENDENCIES AND NOT MRDOCS_BUILD_SHARED" ON OFF) +set_ternary(MRDOCS_DO_REEXPORT_DEPENDENCIES "NOT MRDOCS_DO_BUNDLE_DEPENDENCIES AND NOT MRDOCS_BUILD_SHARED" ON OFF) set_ternary(MRDOCS_GCC "CMAKE_CXX_COMPILER_ID STREQUAL \"GNU\"" ON OFF) set_ternary(MRDOCS_CLANG "CMAKE_CXX_COMPILER_ID MATCHES \"Clang$\"" ON OFF) set_property(GLOBAL PROPERTY USE_FOLDERS ON) - +# MSVC flags shared by every mrdocs target below (not installed consumers): +# - /EHs enables C++ exception handling. The mrdocs tool, the unit tests, and the +# examples are separate targets whose TUs include exception-using STL headers, so +# without it MSVC raises C4530 and /WX makes it fatal; the EH model must also be +# uniform across all TUs, so it belongs here rather than on mrdocs-core alone. +# - /wd4100 accepts unused parameters, an accepted style across mrdocs (visitor / +# tag_invoke / finalizer signatures) that the clang build already allows via +# -Wno-unused-parameter; mirror it so /W4 /WX does not fail on C4100. if (MSVC) - # Suppressions we use to get from LLVM's HandleLLVMOptions.cmake - add_compile_options(/wd4100 /wd4127 /wd4141 /wd4146 /wd4204 /wd4244 /wd4245 /wd4267 /wd4291 /wd4310 /wd4319 /wd4324 /wd4351 /wd4389 /wd4456 /wd4457 /wd4458 /wd4459 /wd4503 /wd4505 /wd4510 /wd4512 /wd4577 /wd4592 /wd4610 /wd4611 /wd4624 /wd4701 /wd4702 /wd4703 /wd4706 /wd4722 /wd4805 /wd5105) - # Conformance flags we used to get from LLVM's HandleLLVMOptions.cmake - add_compile_options(/Zc:__cplusplus /Zc:preprocessor) + add_compile_options(/EHs /wd4100) endif() #------------------------------------------------- @@ -70,14 +85,17 @@ endif() add_subdirectory(utils) add_subdirectory(libs) add_subdirectory(src) -if (MRDOCS_BUILD_HEADERS_ONLY) - return() -else() add_subdirectory(tools) -add_subdirectory(tests) -add_subdirectory(examples) -add_subdirectory(docs) -add_subdirectory(data) -include(utils/cmake/install.cmake) -mrdocs_install() +if (MRDOCS_BUILD_TESTS) + add_subdirectory(tests) +endif() +if (MRDOCS_BUILD_EXAMPLES) + add_subdirectory(examples) +endif() +if (MRDOCS_BUILD_DOCS) + add_subdirectory(docs) +endif() +if (MRDOCS_INSTALL) + add_subdirectory(data) + mrdocs_install() endif() diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index c6078185a5e..0d57d4ad67d 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -9,10 +9,6 @@ # # This directory only installs assets, so it does nothing unless installing. -if (MRDOCS_BUILD_HEADERS_ONLY OR NOT MRDOCS_INSTALL) - return() -endif() - # Install the shared assets this directory owns into the FHS share/ location. # (The LLVM-derived assets, bundled libc++ headers and the clang resource dir, # are installed by src/, next to the dependency that provides them.) @@ -25,7 +21,7 @@ foreach (mrdocs_dir addons) DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/mrdocs FILES_MATCHING PATTERN "*") endforeach () -foreach (data_dir cmake gdb) +foreach (data_dir gdb) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${data_dir} DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/mrdocs FILES_MATCHING PATTERN "*") diff --git a/data/README.md b/data/README.md index f8ce0ad4768..5c2c9d9109b 100644 --- a/data/README.md +++ b/data/README.md @@ -8,6 +8,5 @@ mirrored into the `share/` subtree; this README is not installed. ## Contents - `mrdocs/` — runtime assets installed with MrDocs: `addons/` (Handlebars templates and generator helpers) and `headers/` (bundled libc stubs). -- `cmake/` — `MrDocs.cmake`, the consumer helper for downstream CMake projects. - `gdb/` — GDB pretty-printers for MrDocs types. - `lldb/` — LLDB data formatters for MrDocs types. diff --git a/data/cmake/MrDocs.cmake b/data/cmake/MrDocs.cmake deleted file mode 100644 index fe7a9dda0b8..00000000000 --- a/data/cmake/MrDocs.cmake +++ /dev/null @@ -1,172 +0,0 @@ -# -# 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 Klemens Morgenstern (klemens.morgenstern@gmx.net) -# Copyright (c) 2023 Alan de Freitas (alandefreitas@gmail.com) -# -# Official repository: https://github.com/cppalliance/mrdocs -# - -#[=======================================================================[.rst: -MrDocs ------ - -This module provides a function to generate documentation from C++ source -files with mrdocs. - -It identifies the necessary MrDocs configuration options for the current -project and generates a documentation target. - -See the MrDocs usage documentation for complete instructions. - -#]=======================================================================] - -function(add_mrdocs MRDOCS_TARGET_NAME) - #------------------------------------------------- - # Parse arguments - #------------------------------------------------- - set(options_prefix MRDOCS_TARGET) - set(options EXCLUDE_FROM_ALL) - set(oneValueArgs CONFIG OUTPUT ADDONS) - set(multiValueArgs COMMENT) - cmake_parse_arguments(${options_prefix} "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - set(MRDOCS_TARGET_SOURCES ${MRDOCS_TARGET_UNPARSED_ARGUMENTS}) - - #------------------------------------------------- - # Executable - #------------------------------------------------- - if (NOT DEFINED MRDOCS_EXECUTABLE AND TARGET mrdocs) - set(MRDOCS_EXECUTABLE $) - set(MRDOCS_EXECUTABLE_DEPENDENCY mrdocs) - endif() - if (NOT DEFINED MRDOCS_EXECUTABLE) - find_program(MRDOCS_EXECUTABLE mrdocs) - if (NOT MRDOCS_EXECUTABLE) - message(FATAL_ERROR "MrDocs build script requires mrdocs to be installed") - endif() - endif() - - #------------------------------------------------- - # CMake compile commands - #------------------------------------------------- - if (NOT DEFINED MRDOCS_COMPILE_COMMANDS) - if (NOT CMAKE_EXPORT_COMPILE_COMMANDS) - message(FATAL_ERROR "MrDocs requires either CMAKE_EXPORT_COMPILE_COMMANDS=ON or MRDOCS_COMPILE_COMMANDS to be set") - endif() - set(MRDOCS_COMPILE_COMMANDS ${CMAKE_BINARY_DIR}/compile_commands.json) - endif() - set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) - - #------------------------------------------------- - # EXCLUDE_FROM_ALL - #------------------------------------------------- - if (MRDOCS_TARGET_EXCLUDE_FROM_ALL) - set(MRDOCS_TARGET_ALL_STR "") - else() - set(MRDOCS_TARGET_ALL_STR "ALL") - endif() - - #------------------------------------------------- - # Configuration - #------------------------------------------------- - if (NOT MRDOCS_TARGET_CONFIG) - set(MRDOCS_TARGET_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/mrdocs.yml) - if (NOT EXISTS ${MRDOCS_TARGET_CONFIG}) - foreach (dir doc docs antora) - if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${dir}/mrdocs.yml) - set(MRDOCS_TARGET_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/${dir}/mrdocs.yml) - break() - endif() - endforeach() - endif() - if (NOT EXISTS ${MRDOCS_TARGET_CONFIG}) - foreach (dir ${CMAKE_CURRENT_SOURCE_DIR}) - if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${dir}/mrdocs.yml) - set(MRDOCS_TARGET_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/${dir}/mrdocs.yml) - break() - endif() - endforeach() - endif() - if (NOT EXISTS ${MRDOCS_TARGET_CONFIG}) - message(FATAL_ERROR "MrDocs: CONFIG option not set and no mrdocs.yml found in ${CMAKE_CURRENT_SOURCE_DIR}") - endif() - endif() - get_filename_component(MRDOCS_TARGET_CONFIG ${MRDOCS_TARGET_CONFIG} ABSOLUTE) - - #------------------------------------------------- - # Format - #------------------------------------------------- - if (MRDOCS_TARGET_ADDONS) - if (NOT EXISTS ${MRDOCS_TARGET_ADDONS}) - message(FATAL_ERROR "MrDocs: ADDONS directory ${MRDOCS_TARGET_ADDONS} does not exist") - endif() - endif() - - if (NOT MRDOCS_TARGET_ADDONS) - get_filename_component(MRDOCS_EXECUTABLE_DIR ${MRDOCS_EXECUTABLE} DIRECTORY) - set(${PROJECT_NAME}_DIR ${CMAKE_CURRENT_SOURCE_DIR}) - set(DEFAULT_ADDONS_PATHS - # Project has its own addons - ${${PROJECT_NAME}_DIR}/share/mrdocs/addons - ${${PROJECT_NAME}_DIR}/docs/mrdocs/addons - ${${PROJECT_NAME}_DIR}/docs/addons - ${${PROJECT_NAME}_DIR}/antora/addons - # Relative to mrdocs executable - ${MRDOCS_EXECUTABLE_DIR}/../share/mrdocs/addons # FHS - ${MRDOCS_EXECUTABLE_DIR}/share/mrdocs/addons # FHS with no `bin` - ${MRDOCS_EXECUTABLE_DIR}/../addons # Non-FHS - ${MRDOCS_EXECUTABLE_DIR}/addons # Non-FHS with no `bin` - ) - foreach (dir ${DEFAULT_ADDONS_PATHS}) - message(STATUS "MrDocs: Looking for addons in ${dir}") - if (EXISTS ${dir}) - set(MRDOCS_TARGET_ADDONS ${dir}) - break() - endif() - endforeach() - endif() - - if (NOT MRDOCS_TARGET_ADDONS) - message(FATAL_ERROR "MrDocs: ADDONS directory not set and no addons found in ${CMAKE_CURRENT_SOURCE_DIR} or relative to ${MRDOCS_EXECUTABLE}") - endif() - - #------------------------------------------------- - # Comment - #------------------------------------------------- - if (NOT MRDOCS_TARGET_COMMENT) - set(MRDOCS_TARGET_COMMENT "Generate ${CMAKE_PROJECT_NAME} documentation with MrDocs") - endif() - - #------------------------------------------------- - # Output - #------------------------------------------------- - if (NOT MRDOCS_TARGET_OUTPUT) - set(MRDOCS_TARGET_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/mrdocs) - endif() - - - #------------------------------------------------- - # Custom target - #------------------------------------------------- - message(STATUS "MrDocs: Generating documentation for ${CMAKE_PROJECT_NAME} in ${MRDOCS_TARGET_OUTPUT}") - set(MRDOCS_CMD_LINE_OPTIONS --config=${MRDOCS_TARGET_CONFIG} ${MRDOCS_COMPILE_COMMANDS} - --addons=${MRDOCS_TARGET_ADDONS} --output=${MRDOCS_TARGET_OUTPUT}) - string(REPLACE ";" " " MRDOCS_WS_CMD_LINE_OPTIONS "${MRDOCS_CMD_LINE_OPTIONS}") - message(STATUS "mrdocs ${MRDOCS_WS_CMD_LINE_OPTIONS}") - add_custom_target( - ${MRDOCS_TARGET_NAME} - ${MRDOCS_TARGET_ALL_STR} - COMMAND ${CMAKE_COMMAND} -E make_directory ${MRDOCS_TARGET_OUTPUT} - COMMAND ${CMAKE_COMMAND} -E echo "mrdocs ${MRDOCS_WS_CMD_LINE_OPTIONS}" - COMMAND ${MRDOCS_EXECUTABLE} ${MRDOCS_CMD_LINE_OPTIONS} - DEPENDS ${MRDOCS_TARGET_SOURCES} ${MRDOCS_TARGET_CONFIG} ${MRDOCS_COMPILE_COMMANDS} ${MRDOCS_TARGET_ADDONS} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT ${MRDOCS_TARGET_COMMENT} - USES_TERMINAL - ) - if (MRDOCS_EXECUTABLE_DEPENDENCY) - add_dependencies(${MRDOCS_TARGET_NAME} ${MRDOCS_EXECUTABLE_DEPENDENCY}) - endif() -endfunction() \ No newline at end of file diff --git a/data/mrdocs/headers/libc-stubs/stddef.h b/data/mrdocs/headers/libc-stubs/stddef.h index 0927c7ba2d2..8706c67c4cb 100644 --- a/data/mrdocs/headers/libc-stubs/stddef.h +++ b/data/mrdocs/headers/libc-stubs/stddef.h @@ -89,6 +89,14 @@ typedef unsigned int wint_t; using errno_t = int; // max_align_t +// Guarded with clang's own macro so this stub coexists with clang's +// __stddef_max_align_t.h (from the resource directory): whichever is included +// first defines the type and sets the guard, and the other then skips it. +// Without the guard, both define an anonymous-struct max_align_t and clang +// rejects the second as a typedef redefinition with a different type. +#if !defined(__CLANG_MAX_ALIGN_T_DEFINED) && !defined(_GCC_MAX_ALIGN_T) +#define __CLANG_MAX_ALIGN_T_DEFINED +#define _GCC_MAX_ALIGN_T #if defined(_MSC_VER) typedef double max_align_t; #elif defined(__APPLE__) @@ -105,6 +113,7 @@ typedef struct { __attribute__((__aligned__(__alignof__(long double)))); } max_align_t; #endif +#endif // max_align_t guard // offsetof_t #define offsetof(t, d) __builtin_offsetof(t, d) diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 12ef41e8792..298e0968017 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -11,30 +11,30 @@ # The documentation (Antora) build. This is a regular build: when # MRDOCS_BUILD_DOCS is on we add custom targets that run mrdocs over its own # headers (the reference) and Antora over the site. The separate "compile every -# public header" build is the MrDocs build, MRDOCS_BUILD_HEADERS_ONLY, and lives in -# src/ (it compiles a different target and links nothing). - -if (MRDOCS_BUILD_HEADERS_ONLY OR NOT MRDOCS_BUILD_DOCS) - return() -endif() +# public header" build is the MrDocs build (MRDOCS_MRDOCS_BUILD), and lives in +# docs/mrdocs-build.cmake (it compiles a different target and links nothing). # ---- Reference (mrdocs run over its own headers) ---- if (MRDOCS_GENERATE_REFERENCE) - include(${PROJECT_SOURCE_DIR}/data/cmake/MrDocs.cmake) set(MRDOCS_REFERENCE_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/reference) file(GLOB_RECURSE REFERENCE_SOURCES CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/include/*.hpp ${PROJECT_SOURCE_DIR}/include/*.inc) set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) - add_mrdocs(generate_reference - CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/mrdocs.yml + # Run the freshly built mrdocs over its own public headers. In-tree we point + # it at the source-tree addons and the bundled libc++ / libc-stub headers + # (LIBCXX_DIR comes from src/), so extraction does not depend on the host + # toolchain. The config's compilation database drives which sources are read; + # REFERENCE_SOURCES is a DEPENDS list so the target reruns when they change. + add_custom_target(generate_reference ALL + COMMAND ${CMAKE_COMMAND} -E make_directory ${MRDOCS_REFERENCE_OUTPUT_DIR} + COMMAND $ + --config=${CMAKE_CURRENT_SOURCE_DIR}/mrdocs.yml + --output=${MRDOCS_REFERENCE_OUTPUT_DIR} + ${MRDOCS_BUILTIN_DIR_ARGS} + DEPENDS ${REFERENCE_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/mrdocs.yml COMMENT "Generate MrDocs reference" - OUTPUT ${MRDOCS_REFERENCE_OUTPUT_DIR} - # This in-tree target runs before install, so no build-tree share/ - # is staged; point add_mrdocs at the addons in the source tree - # (they moved to data/mrdocs/addons in the layout refactor) instead - # of letting the helper fall through to its "ADDONS not set" error. - ADDONS ${PROJECT_SOURCE_DIR}/data/mrdocs/addons - ${REFERENCE_SOURCES}) + USES_TERMINAL) + add_dependencies(generate_reference mrdocs) if (MRDOCS_GENERATE_ANTORA_REFERENCE) set(MRDOCS_ANTORA_REFERENCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/modules/ROOT/pages/reference) add_custom_target(generate_antora_reference diff --git a/docs/modules/ROOT/attachments/schemas/config/mrdocs.schema.json b/docs/modules/ROOT/attachments/schemas/config/mrdocs.schema.json index beeb44ba482..c8cbbb9b18b 100644 --- a/docs/modules/ROOT/attachments/schemas/config/mrdocs.schema.json +++ b/docs/modules/ROOT/attachments/schemas/config/mrdocs.schema.json @@ -63,6 +63,12 @@ "title": "Base URL for links to source code", "type": "string" }, + "clang-resource-dir": { + "default": "/share/mrdocs/headers/clang", + "description": "Directory holding Clang's builtin headers (`stddef.h`, `stdarg.h`, and the other compiler intrinsics) in its `include/` subdirectory. MrDocs bundles the resource directory that matches its embedded Clang and passes it as `-resource-dir`, so parsing does not depend on a Clang installation on the host. Leave empty to let Clang locate its own resource directory.", + "title": "Clang resource directory", + "type": "string" + }, "cmake": { "default": "", "description": "When the compilation-database option is a CMakeLists.txt file, these arguments are passed to the cmake command to generate the compilation_database.json.", diff --git a/docs/modules/ROOT/pages/contribute/codebase-tour.adoc b/docs/modules/ROOT/pages/contribute/codebase-tour.adoc index 45f0d340a06..b3e57fb1e41 100644 --- a/docs/modules/ROOT/pages/contribute/codebase-tour.adoc +++ b/docs/modules/ROOT/pages/contribute/codebase-tour.adoc @@ -56,7 +56,6 @@ This directory contains shared resources for the documentation generators and ut Its subdirectories are installed in the `share` directory of the installation. * `data/`—Shared resources for the documentation generators -* `data/cmake/`—CMake modules to generate the documentation * `data/gdb/`—GDB pretty printers * `data/mrdocs/`—Shared resources for the documentation generators diff --git a/docs/modules/ROOT/pages/extensions/as-library.adoc b/docs/modules/ROOT/pages/extensions/as-library.adoc index 06d1e8c56af..4039762c3d6 100644 --- a/docs/modules/ROOT/pages/extensions/as-library.adoc +++ b/docs/modules/ROOT/pages/extensions/as-library.adoc @@ -6,14 +6,12 @@ A {cpp} program can also link the cpp:mrdocs[mrdocs-core] library, drive corpus == Build integration -cpp:mrdocs[mrdocs-core] is exported through a CMake package config: +cpp:mrdocs[mrdocs-core] is exported through a CMake package config. The `breaking-changes` example below links it exactly this way: +.`examples/library/breaking-changes/CMakeLists.txt` [source,cmake] ---- -find_package(mrdocs REQUIRED CONFIG) -add_executable(my_tool main.cpp) -target_link_libraries(my_tool PRIVATE mrdocs::mrdocs-core) -target_compile_features(my_tool PRIVATE cxx_std_23) +include::example$examples/library/breaking-changes/CMakeLists.txt[tags=package;target] ---- == Building a corpus diff --git a/docs/mrdocs-build.cmake b/docs/mrdocs-build.cmake new file mode 100644 index 00000000000..3ffb5776803 --- /dev/null +++ b/docs/mrdocs-build.cmake @@ -0,0 +1,79 @@ +# +# 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) 2026 Alan de Freitas (alandefreitas@gmail.com) +# +# Official repository: https://github.com/cppalliance/mrdocs +# + +# The MrDocs build (MRDOCS_MRDOCS_BUILD): compile a single translation unit that +# includes every public header, producing a compile_commands.json that covers the +# whole API so mrdocs can document itself. The root includes this file and returns +# right after, so none of the real-library CMake runs and nothing is linked. This +# is NOT the Antora docs build (MRDOCS_BUILD_DOCS). + +# ---- Generated headers ---- +# The public headers reference generated ones (e.g. Config.hpp includes +# ), so produce them now, at configure time, before the +# glob below picks them up. utils/codegen is not on the path in this build. +find_program(PYTHON_EXECUTABLE python3 python) +if (NOT PYTHON_EXECUTABLE) + message(FATAL_ERROR "Python is needed to configure mrdocs") +endif() +find_package(Git QUIET) +execute_process( + COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/utils/codegen/generate-version-header.py + ${PROJECT_SOURCE_DIR}/include/mrdocs/Version.hpp.in + ${PROJECT_BINARY_DIR}/include/mrdocs/Version.hpp + --version ${PROJECT_VERSION} + --name ${PROJECT_NAME} + --description "${PROJECT_DESCRIPTION}" + --source-dir ${PROJECT_SOURCE_DIR} + --git "${GIT_EXECUTABLE}" + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMAND_ERROR_IS_FATAL ANY) +execute_process( + COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/utils/codegen/generate-config-info.py + ${PROJECT_SOURCE_DIR}/src/mrdocs/ConfigOptions.json ${PROJECT_BINARY_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMAND_ERROR_IS_FATAL ANY) +execute_process( + COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/utils/codegen/generate-yaml-schema.py + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMAND_ERROR_IS_FATAL ANY) + +# ---- mrdocs-reference ---- +# One TU including every public header: mrdocs's own (source + generated) plus the +# dom and handlebars libraries it re-exports. +file(GLOB_RECURSE MRDOCS_REFERENCE_HEADERS + "${PROJECT_SOURCE_DIR}/include/*.hpp" + "${PROJECT_BINARY_DIR}/include/*.hpp" + "${PROJECT_SOURCE_DIR}/libs/dom/include/*.hpp" + "${PROJECT_SOURCE_DIR}/libs/handlebars/include/*.hpp") + +set(MRDOCS_REFERENCE_CPP "${PROJECT_BINARY_DIR}/mrdocs-reference.cpp") +file(WRITE "${MRDOCS_REFERENCE_CPP}" "// This file is generated automatically by CMake\n\n") +foreach (header IN LISTS MRDOCS_REFERENCE_HEADERS) + file(TO_CMAKE_PATH "${header}" header) + file(APPEND "${MRDOCS_REFERENCE_CPP}" "#include \"${header}\"\n") +endforeach () + +add_library(mrdocs-reference STATIC "${MRDOCS_REFERENCE_CPP}") +target_compile_features(mrdocs-reference PRIVATE cxx_std_23) +target_include_directories(mrdocs-reference PRIVATE + "${PROJECT_SOURCE_DIR}/include" + "${PROJECT_BINARY_DIR}/include" + "${PROJECT_SOURCE_DIR}/libs/polyfill/include" + "${PROJECT_SOURCE_DIR}/libs/dom/include" + "${PROJECT_SOURCE_DIR}/libs/handlebars/include") +target_compile_definitions(mrdocs-reference PRIVATE MRDOCS_STATIC_LINK) +# This target only needs the headers to compile, not to be warning-clean, so +# silence all warnings. The /Zc flags stay: they are needed to compile, not to +# quiet warnings (Platform.hpp gates on __cplusplus, Describe.hpp uses __VA_OPT__). +if (MSVC) + target_compile_options(mrdocs-reference PRIVATE /Zc:__cplusplus /Zc:preprocessor /W0) +else () + target_compile_options(mrdocs-reference PRIVATE -w) +endif () diff --git a/docs/mrdocs.yml b/docs/mrdocs.yml index 9e2eaaecca0..2f82dca5dcc 100644 --- a/docs/mrdocs.yml +++ b/docs/mrdocs.yml @@ -12,6 +12,7 @@ input: - ../include - ../libs/dom/include - ../libs/handlebars/include + - ../libs/polyfill/include file-patterns: - '*.hpp' include-symbols: @@ -32,5 +33,5 @@ implementation-defined: - '**mrdocs_*_descriptor_fn' multipage: true generator: adoc -cmake: '-D MRDOCS_BUILD_HEADERS_ONLY=ON' +cmake: '-D MRDOCS_MRDOCS_BUILD=ON' diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index bf06b62648e..94c3dc716a0 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -8,8 +8,17 @@ # Official repository: https://github.com/cppalliance/mrdocs # -if (MRDOCS_BUILD_HEADERS_ONLY OR NOT MRDOCS_BUILD_EXAMPLES) - return() +# Examples are ordinary consumer code compiled under the project's /W4 /WX. Some +# include mrdocs's public headers, whose tag_invoke overloads (the ADL tag is an +# unreferenced parameter, C4100) and generated schema (C4245) raise the same /W4 +# diagnostics the libraries do; the rest hit the usual conversion/shadowing +# noise. Suppress that set here at the examples' directory scope, the same way +# libs/ does for its own code, so it stays out of the root list and off +# mrdocs-core's exported interface. LLVM header noise is not needed: examples do +# not compile LLVM headers. +if (MSVC) + add_compile_options(/wd4100 /wd4127 /wd4244 /wd4245 /wd4267 + /wd4456 /wd4457 /wd4458 /wd4459 /wd4701 /wd4702 /wd4703) endif() # Each example subdirectory owns the tests that exercise it. diff --git a/examples/configuration/CMakeLists.txt b/examples/configuration/CMakeLists.txt index b170c83fc49..315e2238d7c 100644 --- a/examples/configuration/CMakeLists.txt +++ b/examples/configuration/CMakeLists.txt @@ -25,8 +25,8 @@ foreach (example IN LISTS _config_examples) add_test( NAME mrdocs-configuration-${_name} COMMAND bash run.sh - --addons=${CMAKE_SOURCE_DIR}/data/mrdocs/addons --output=${CMAKE_CURRENT_BINARY_DIR}/${_name}/reference-output + ${MRDOCS_BUILTIN_DIR_ARGS} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${example} ) set_property(TEST mrdocs-configuration-${_name} PROPERTY diff --git a/examples/dependencies/CMakeLists.txt b/examples/dependencies/CMakeLists.txt index dbc4154040b..a477b582e75 100644 --- a/examples/dependencies/CMakeLists.txt +++ b/examples/dependencies/CMakeLists.txt @@ -14,8 +14,8 @@ foreach (example IN ITEMS find shim-files shim-snippets accept-missing) add_test( NAME mrdocs-dependencies-${example} COMMAND mrdocs docs/mrdocs.yml - --addons=${CMAKE_SOURCE_DIR}/data/mrdocs/addons --output ${CMAKE_CURRENT_BINARY_DIR}/${example}/reference-output + ${MRDOCS_BUILTIN_DIR_ARGS} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${example} ) endforeach () diff --git a/examples/generators/CMakeLists.txt b/examples/generators/CMakeLists.txt index 2efa882dfcf..c0c428798e7 100644 --- a/examples/generators/CMakeLists.txt +++ b/examples/generators/CMakeLists.txt @@ -22,9 +22,7 @@ foreach (data_driven IN ITEMS md tex jsonl) "${CMAKE_CURRENT_SOURCE_DIR}/data-driven/${data_driven}/simple.cpp" "--config=${CMAKE_CURRENT_SOURCE_DIR}/data-driven/${data_driven}/mrdocs.yml" "--output=${CMAKE_CURRENT_BINARY_DIR}/data-driven/${data_driven}" - "--addons=${CMAKE_SOURCE_DIR}/data/mrdocs/addons" - "--stdlib-includes=${LIBCXX_DIR}" - "--libc-includes=${CMAKE_SOURCE_DIR}/data/mrdocs/headers/libc-stubs" + ${MRDOCS_BUILTIN_DIR_ARGS} --log-level=warn ) endforeach () @@ -34,8 +32,8 @@ foreach (script_driven IN ITEMS search-index json) add_test( NAME mrdocs-generator-script-driven-${script_driven} COMMAND bash run.sh - --addons=${CMAKE_SOURCE_DIR}/data/mrdocs/addons --output=${CMAKE_CURRENT_BINARY_DIR}/script-driven/${script_driven}/reference-output + ${MRDOCS_BUILTIN_DIR_ARGS} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/script-driven/${script_driven} ) set_property(TEST mrdocs-generator-script-driven-${script_driven} PROPERTY diff --git a/examples/getting-started/CMakeLists.txt b/examples/getting-started/CMakeLists.txt index 8559204dc11..53cffd7ae6f 100644 --- a/examples/getting-started/CMakeLists.txt +++ b/examples/getting-started/CMakeLists.txt @@ -15,8 +15,8 @@ foreach (example IN ITEMS compilation-database cmake cmake-header-only scanned) add_test( NAME mrdocs-getting-started-${example} COMMAND bash run.sh - --addons=${CMAKE_SOURCE_DIR}/data/mrdocs/addons --output=${CMAKE_CURRENT_BINARY_DIR}/${example}/reference-output + ${MRDOCS_BUILTIN_DIR_ARGS} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${example} ) set_property(TEST mrdocs-getting-started-${example} PROPERTY diff --git a/examples/library/breaking-changes/CMakeLists.txt b/examples/library/breaking-changes/CMakeLists.txt index d68fe988a4a..ddc347c8cf6 100644 --- a/examples/library/breaking-changes/CMakeLists.txt +++ b/examples/library/breaking-changes/CMakeLists.txt @@ -1,11 +1,32 @@ +# +# 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) 2026 Alan de Freitas (alandefreitas@gmail.com) +# +# Official repository: https://github.com/cppalliance/mrdocs +# + +# If not inside the Mr.Docs source tree. +if (NOT TARGET mrdocs::mrdocs-core) + cmake_minimum_required(VERSION 3.20) + project(mrdocs-breaking-changes-example LANGUAGES CXX) + # tag::package[] + find_package(mrdocs REQUIRED CONFIG) + # end::package[] + include(CTest) +endif () + +# tag::target[] add_executable(mrdocs-breaking-changes-example src/main.cpp src/Corpus.cpp src/Diff.cpp src/BreakingChangesGenerator.cpp ) -target_link_libraries(mrdocs-breaking-changes-example PRIVATE mrdocs-core) -target_compile_features(mrdocs-breaking-changes-example PRIVATE cxx_std_23) +target_link_libraries(mrdocs-breaking-changes-example PRIVATE mrdocs::mrdocs-core) +# end::target[] if (MRDOCS_CLANG) target_compile_options(mrdocs-breaking-changes-example PRIVATE -Wno-covered-switch-default) endif() @@ -19,8 +40,7 @@ if (BUILD_TESTING) "-DV2_CONFIG=${CMAKE_CURRENT_SOURCE_DIR}/test/v2/mrdocs.yml" "-DEXPECTED=${CMAKE_CURRENT_SOURCE_DIR}/test/expected.txt" "-DACTUAL=${CMAKE_CURRENT_BINARY_DIR}/actual.txt" - "-DDATA_DIR=${PROJECT_SOURCE_DIR}/data" - "-DSTAGE_ROOT=${CMAKE_CURRENT_BINARY_DIR}/mrdocs-root" + "-DBUILTIN_ARGS=${MRDOCS_BUILTIN_DIR_ARGS}" -P "${CMAKE_CURRENT_SOURCE_DIR}/test/run.cmake" ) endif() diff --git a/examples/library/breaking-changes/src/Corpus.cpp b/examples/library/breaking-changes/src/Corpus.cpp index 8a89da46b10..6fc09403cad 100644 --- a/examples/library/breaking-changes/src/Corpus.cpp +++ b/examples/library/breaking-changes/src/Corpus.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace mrdocs::example { @@ -17,13 +18,19 @@ namespace mrdocs::example { Expected loadCorpusFromConfig( std::string const& configPath, - ReferenceDirectories const& dirs) + ReferenceDirectories const& dirs, + char const** argv) { Config config; ReferenceDirectories localDirs = dirs; localDirs.cwd = std::string(files::getParentDir(configPath)); - MRDOCS_TRY(Config::load_file(config, configPath)); - MRDOCS_TRY(config.normalize(localDirs)); + // Command-line overrides (if any) are applied on top of the config file and + // before normalization. Running the example against an installed MrDocs + // needs none; the in-tree test forwards the built-in directory flags here. + MRDOCS_TRY(Config::load_file(config, configPath, localDirs, argv)); + // load_file applies the config's log level; this example only wants the + // breaking-change report, so keep extraction diagnostics quiet. + report::setMinimumLevel(report::Level::error); return Corpus::build(config); } // end::load-corpus[] diff --git a/examples/library/breaking-changes/src/Corpus.hpp b/examples/library/breaking-changes/src/Corpus.hpp index 835734b0da7..97999489872 100644 --- a/examples/library/breaking-changes/src/Corpus.hpp +++ b/examples/library/breaking-changes/src/Corpus.hpp @@ -26,7 +26,8 @@ namespace mrdocs::example { Expected loadCorpusFromConfig( std::string const& configPath, - ReferenceDirectories const& dirs); + ReferenceDirectories const& dirs, + char const** argv = nullptr); } // namespace mrdocs::example diff --git a/examples/library/breaking-changes/src/main.cpp b/examples/library/breaking-changes/src/main.cpp index 657da443425..d9778d80f69 100644 --- a/examples/library/breaking-changes/src/main.cpp +++ b/examples/library/breaking-changes/src/main.cpp @@ -47,18 +47,20 @@ int main(int argc, char const** argv) report::setMinimumLevel(report::Level::error); + // A default-constructed ReferenceDirectories points at MrDocs's installed + // root (linking mrdocs-core bakes it in), so the built-in resources resolve + // with no configuration. Any arguments after the two configs are option + // overrides passed straight through: an installed run supplies none, while + // the in-tree test forwards the built-in directory flags. ReferenceDirectories dirs; - if (char const* root = std::getenv("MRDOCS_ROOT")) - { - dirs.mrdocsRoot = root; - } - auto v1Corpus = example::loadCorpusFromConfig(argv[1], dirs); + char const** overrides = argv + 3; + auto v1Corpus = example::loadCorpusFromConfig(argv[1], dirs, overrides); if (!v1Corpus) { std::println(stderr, "v1: {}", v1Corpus.error().reason()); return 1; } - auto v2Corpus = example::loadCorpusFromConfig(argv[2], dirs); + auto v2Corpus = example::loadCorpusFromConfig(argv[2], dirs, overrides); if (!v2Corpus) { std::println(stderr, "v2: {}", v2Corpus.error().reason()); diff --git a/examples/library/breaking-changes/test/run.cmake b/examples/library/breaking-changes/test/run.cmake index 90bff174f4e..ac6629aa3bc 100644 --- a/examples/library/breaking-changes/test/run.cmake +++ b/examples/library/breaking-changes/test/run.cmake @@ -1,21 +1,23 @@ +# +# 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) 2026 Alan de Freitas (alandefreitas@gmail.com) +# +# Official repository: https://github.com/cppalliance/mrdocs +# + if(NOT TOOL OR NOT V1_CONFIG OR NOT V2_CONFIG OR NOT EXPECTED OR NOT ACTUAL) message(FATAL_ERROR "run.cmake: missing required variable") endif() -# The example resolves shared assets from /share/mrdocs (the -# installed layout). In the source tree those assets live under data/, so -# stage a root whose share/ mirrors data/ and point the tool at it. This lets -# the example keep demonstrating the default install-layout resolution. -if(DATA_DIR AND STAGE_ROOT) - file(REMOVE_RECURSE "${STAGE_ROOT}/share") - file(COPY "${DATA_DIR}/" DESTINATION "${STAGE_ROOT}/share") - set(ENV{MRDOCS_ROOT} "${STAGE_ROOT}") -else() - set(ENV{MRDOCS_ROOT} "${MRDOCS_ROOT}") -endif() - +# An installed consumer resolves MrDocs's resources from its root with no extra +# arguments. Running in-tree there is no /share/mrdocs yet, so forward the +# built-in directory flags (BUILTIN_ARGS) as option overrides -- no copy, no +# machine paths baked into the example binary. execute_process( - COMMAND "${TOOL}" "${V1_CONFIG}" "${V2_CONFIG}" + COMMAND "${TOOL}" "${V1_CONFIG}" "${V2_CONFIG}" ${BUILTIN_ARGS} OUTPUT_FILE "${ACTUAL}" RESULT_VARIABLE _rc ) diff --git a/include/mrdocs/Config/ReferenceDirectories.hpp b/include/mrdocs/Config/ReferenceDirectories.hpp index 2f117cf6afa..f11bfda331f 100644 --- a/include/mrdocs/Config/ReferenceDirectories.hpp +++ b/include/mrdocs/Config/ReferenceDirectories.hpp @@ -4,7 +4,7 @@ // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // -// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// Copyright (c) 2026 Alan de Freitas (alandefreitas@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // @@ -12,9 +12,9 @@ #ifndef MRDOCS_API_CONFIG_REFERENCEDIRECTORIES_HPP #define MRDOCS_API_CONFIG_REFERENCEDIRECTORIES_HPP +#include #include - namespace mrdocs { /** Reference directories used to resolve paths @@ -30,9 +30,25 @@ struct ReferenceDirectories /** Absolute path to the current working directory. */ std::string cwd; - /** Absolute path to the MrDocs repository root. + /** Absolute path to the MrDocs repository/install root. + + All of MrDocs's built-in directories (addons and the parse-time header + sets) derive from this: `/share/mrdocs/...`. */ std::string mrdocsRoot; + + /** Construct and resolve the reference directories. + + The default root is the compile-time MRDOCS_DEFAULT_ROOT, which + mrdocs-config.cmake injects for a downstream project. It is a default + argument, so it is evaluated in the caller's translation unit -- that is + what lets a linking project's find_package-provided prefix reach here. + The constructor then refines it: MRDOCS_ROOT in the environment wins; + otherwise an empty root falls back to the running executable's location. + Defined in mrdocs-core to keep the platform/LLVM lookups out of this + header. + */ + MRDOCS_DECL explicit ReferenceDirectories(std::string root = MRDOCS_DEFAULT_ROOT); }; } // mrdocs diff --git a/include/mrdocs/Metadata/Specifiers/NoexceptInfo.hpp b/include/mrdocs/Metadata/Specifiers/NoexceptInfo.hpp index 00fad3b63e0..54c8edf7839 100644 --- a/include/mrdocs/Metadata/Specifiers/NoexceptInfo.hpp +++ b/include/mrdocs/Metadata/Specifiers/NoexceptInfo.hpp @@ -68,7 +68,7 @@ toString( inline void tag_invoke( - dom::ValueFromTag tag, + dom::ValueFromTag, dom::Value& v, NoexceptInfo const& info) { diff --git a/include/mrdocs/Platform.hpp b/include/mrdocs/Platform.hpp index d7b8e75ab83..b36e1bab308 100644 --- a/include/mrdocs/Platform.hpp +++ b/include/mrdocs/Platform.hpp @@ -85,6 +85,11 @@ namespace mrdocs { # endif #endif +// Expected root of the MrDocs installation when package is found +#ifndef MRDOCS_DEFAULT_ROOT +#define MRDOCS_DEFAULT_ROOT "" +#endif + } // mrdocs diff --git a/include/mrdocs/Support/Reflection/Describe.hpp b/include/mrdocs/Support/Reflection/Describe.hpp index eb2948ecea4..75edf9ca5dc 100644 --- a/include/mrdocs/Support/Reflection/Describe.hpp +++ b/include/mrdocs/Support/Reflection/Describe.hpp @@ -460,7 +460,7 @@ enumeratorKebabSize() { std::string_view name; for_each(describe_enumerators{}, - [&](auto const& D) { if (D.value == V) { name = D.name; } }); + [&](auto const& D) { if constexpr (std::remove_cvref_t::value == V) { name = D.name; } }); return toKebabCase(name).size(); } @@ -471,7 +471,7 @@ enumeratorKebabArray() std::array()> arr{}; std::string_view name; for_each(describe_enumerators{}, - [&](auto const& D) { if (D.value == V) { name = D.name; } }); + [&](auto const& D) { if constexpr (std::remove_cvref_t::value == V) { name = D.name; } }); std::string const kebab = toKebabCase(name); for (std::size_t i = 0; i < arr.size(); ++i) { arr[i] = kebab[i]; } return arr; diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 1ac50e6c2af..a4c11f86d19 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -8,12 +8,6 @@ # Official repository: https://github.com/cppalliance/mrdocs # -# The header-only "MrDocs build" links nothing, so these libraries are not -# needed there. -if (MRDOCS_BUILD_HEADERS_ONLY) - return() -endif() - # Self-contained libraries, each with its own include/ + src/ (+ tests/) and its # own CMakeLists. These depend ONLY on the standard library, on the `polyfills` # library (standard-library features not yet available on every supported @@ -38,6 +32,13 @@ elseif (MSVC) # puts the compiler in conformance mode so /W4 diagnostics match those under # which mrdocs-core builds this same code. These match mrdocs-core's options. add_compile_options(/permissive- /EHs /MP) + # /W4 diagnostics these libraries' own std-based code raises. Unlike the LLVM + # header suppressions (which ride on mrdocs-llvm in src/), these are about our + # code, so they belong with the libraries. Kept narrow on purpose: C4100 + # unreferenced parameter, C4244/C4245/C4267 conversions, C4127 constant + # condition, C4456/C4457/C4458/C4459 shadowing, C4701/C4702/C4703 flow. + add_compile_options(/wd4100 /wd4127 /wd4244 /wd4245 /wd4267 + /wd4456 /wd4457 /wd4458 /wd4459 /wd4701 /wd4702 /wd4703) # On develop this code lived in mrdocs-core and inherited these Windows # defines from the global add_definitions(${LLVM_DEFINITIONS}); as decoupled # libraries that never link LLVM, they must set them directly. diff --git a/libs/polyfill/include/mrdocs/polyfill/expected.hpp b/libs/polyfill/include/mrdocs/polyfill/expected.hpp index ee57fb49da3..76115a39a57 100644 --- a/libs/polyfill/include/mrdocs/polyfill/expected.hpp +++ b/libs/polyfill/include/mrdocs/polyfill/expected.hpp @@ -301,6 +301,9 @@ unexpected(E) -> unexpected; this single overload and there is no ADL ambiguity across namespaces. It yields exactly `unexpected`, which `expected` recognizes (a derived type would not be, breaking `expected`). + + @param e The error value to wrap. + @return An `unexpected` holding the decayed error value. */ template constexpr unexpected> diff --git a/libs/polyfill/include/mrdocs/polyfill/type_traits.hpp b/libs/polyfill/include/mrdocs/polyfill/type_traits.hpp index 8d33bbc25c5..cb5672725d0 100644 --- a/libs/polyfill/include/mrdocs/polyfill/type_traits.hpp +++ b/libs/polyfill/include/mrdocs/polyfill/type_traits.hpp @@ -18,12 +18,30 @@ #include #include +/** Standard-library polyfills. + + Stand-ins for standard-library features not yet available on every supported + compiler. Each becomes an alias to the standard version once that feature is + universally available. +*/ namespace mrdocs::polyfill { +// The doc comments are repeated in both branches because a doc comment must +// immediately precede its declaration to attach: clang will not associate one +// across the intervening #ifdef/#else text. #ifdef __cpp_lib_reference_from_temporary -using std::reference_constructs_from_temporary_v; +/** `true` if a reference of type `To` would bind to a temporary materialized + from an expression of type `From`. Mirrors `std::reference_converts_from_temporary_v`. +*/ using std::reference_converts_from_temporary_v; +/** `true` if binding a reference of type `To` to an expression of type `From` + would bind to a temporary. Mirrors `std::reference_constructs_from_temporary_v`. +*/ +using std::reference_constructs_from_temporary_v; #else +/** `true` if a reference of type `To` would bind to a temporary materialized + from an expression of type `From`. Mirrors `std::reference_converts_from_temporary_v`. +*/ template concept reference_converts_from_temporary_v = std::is_reference_v @@ -36,6 +54,9 @@ concept reference_converts_from_temporary_v && std::is_convertible_v&&> && !std::is_convertible_v&>) ); +/** `true` if binding a reference of type `To` to an expression of type `From` + would bind to a temporary. Mirrors `std::reference_constructs_from_temporary_v`. +*/ template concept reference_constructs_from_temporary_v = reference_converts_from_temporary_v; diff --git a/mrdocs-config.cmake.in b/mrdocs-config.cmake.in index 346acb91ba2..3f6396794c6 100644 --- a/mrdocs-config.cmake.in +++ b/mrdocs-config.cmake.in @@ -1,41 +1,56 @@ +# +# 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) 2026 Alan de Freitas (alandefreitas@gmail.com) +# +# Official repository: https://github.com/cppalliance/mrdocs +# + @PACKAGE_INIT@ -# How mrdocs installation was built -set(MRDOCS_BUILT_SHARED "@BUILD_SHARED_LIBS@") +# How this mrdocs installation was built. +# Inspect these to decide if the toolchain matches the binaries. +set(MRDOCS_BUILT_SHARED "@MRDOCS_BUILD_SHARED@") +set(MRDOCS_BUILT_BUNDLED "@MRDOCS_DO_BUNDLE_DEPENDENCIES@") +set(MRDOCS_BUILT_REEXPORT_DEPENDENCIES "@MRDOCS_DO_REEXPORT_DEPENDENCIES@") set(MRDOCS_BUILT_CXX_COMPILER_ID "@CMAKE_CXX_COMPILER_ID@") set(MRDOCS_BUILT_CXX_COMPILER_VERSION "@CMAKE_CXX_COMPILER_VERSION@") -# Paths -set_and_check(MRDOCS_INSTALL_DIR "@PACKAGE_CMAKE_INSTALL_LIBDIR@") -set_and_check(MRDOCS_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@") -set_and_check(MRDOCS_LIB_DIR "@PACKAGE_LIB_INSTALL_DIR@") - -# Set module paths -include(CMakeFindDependencyMacro) -list(APPEND CMAKE_MODULE_PATH ${MRDOCS_CONFIG_INSTALL_DIR}) -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") -list(APPEND CMAKE_MODULE_PATH "${MRDOCS_INCLUDE_DIR}") -list(APPEND CMAKE_MODULE_PATH "${MRDOCS_INSTALL_DIR}") -list(APPEND CMAKE_MODULE_PATH "${CMAKE_INSTALL_DATAROOTDIR}/mrdocs/cmake") - -# Find dependencies -find_dependency(LLVM) -find_dependency(Clang) -find_dependency(@DUKTAPE_PACKAGE_NAME@) -find_dependency(fmt) - -# Create imported targets +# Installation paths, relocated relative to this file by @PACKAGE_INIT@. +set_and_check(MRDOCS_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@") # e.g. "include" +set_and_check(MRDOCS_LIB_DIR "@PACKAGE_LIB_INSTALL_DIR@") # e.g. "lib" or "lib64" +set_and_check(MRDOCS_DATAROOT_DIR "@PACKAGE_DATAROOT_INSTALL_DIR@") # e.g. "share" +set(MRDOCS_STDLIB_INCLUDE_DIR "@PACKAGE_DATAROOT_INSTALL_DIR@/mrdocs/headers/libcxx") +set(MRDOCS_LIBC_INCLUDE_DIR "@PACKAGE_DATAROOT_INSTALL_DIR@/mrdocs/headers/libc-stubs") + +# Dependencies +if (MRDOCS_BUILT_REEXPORT_DEPENDENCIES) + include(CMakeFindDependencyMacro) + find_dependency(LLVM CONFIG) + find_dependency(Clang CONFIG) + find_dependency(jerryscript CONFIG) + find_dependency(Lua CONFIG) +endif () + +# Imported targets. include("${CMAKE_CURRENT_LIST_DIR}/mrdocs-targets.cmake") -check_required_components(mrdocs) -# Set executable path. Only define it when the install actually captured one: -# a defined-but-empty value would suppress the find_program fallback in -# MrDocs.cmake (which guards on `if(NOT DEFINED MRDOCS_EXECUTABLE)`) and leave -# add_mrdocs() consumers with an empty command. When it is empty, leave the -# variable undefined so that fallback locates the installed mrdocs on the -# consumer's CMAKE_PREFIX_PATH. -set(_mrdocs_executable "@MRDOCS_EXECUTABLE@") -if (_mrdocs_executable) +# Bake the relocated MrDocs root into consumers, so a default-constructed +# ReferenceDirectories in a program that links mrdocs-core finds the bundled +# resources under /share/mrdocs with no runtime configuration. +if (TARGET mrdocs::mrdocs-core) + target_compile_definitions(mrdocs::mrdocs-core INTERFACE + "MRDOCS_DEFAULT_ROOT=\"${PACKAGE_PREFIX_DIR}\"") +endif () + +# Locate the installed mrdocs executable so a consumer can invoke it (e.g. from +# its own add_custom_target that generates documentation). +set(_mrdocs_executable "@PACKAGE_BIN_INSTALL_DIR@/mrdocs@CMAKE_EXECUTABLE_SUFFIX@") +if (EXISTS "${_mrdocs_executable}") set(MRDOCS_EXECUTABLE "${_mrdocs_executable}") endif () unset(_mrdocs_executable) + +check_required_components(mrdocs) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f562e088ded..baa59d5b1e9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,123 +9,21 @@ # #------------------------------------------------- -# MrDocs header-compile build (MRDOCS_BUILD_HEADERS_ONLY) +# Dependencies #------------------------------------------------- -# A shortcut that compiles a single translation unit including every public -# header, producing a compile_commands.json that covers the whole API so -# mrdocs can document itself. It links nothing (no LLVM, no libs/), so it is -# defined here before the dependencies are even found, and we return so none of -# the real build runs. This is NOT the Antora docs build (MRDOCS_BUILD_DOCS). -if (MRDOCS_BUILD_HEADERS_ONLY) - set(SRC_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/include") - file(GLOB_RECURSE SRC_HEADER_FILES - "${SRC_INCLUDE_DIR}/*.hpp" - "${PROJECT_SOURCE_DIR}/libs/dom/include/*.hpp" - "${PROJECT_SOURCE_DIR}/libs/handlebars/include/*.hpp") - set(BIN_INCLUDE_DIR "${PROJECT_BINARY_DIR}/include") - file(GLOB_RECURSE BIN_HEADER_FILES "${BIN_INCLUDE_DIR}/*.hpp") - set(ALL_HEADER_FILES ${SRC_HEADER_FILES} ${BIN_HEADER_FILES}) - - set(TEMP_CPP_FILE "${PROJECT_BINARY_DIR}/all_headers.cpp") - file(WRITE "${TEMP_CPP_FILE}" "// This file is generated automatically by CMake\n\n") - foreach(HEADER_FILE IN LISTS ALL_HEADER_FILES) - file(TO_CMAKE_PATH "${HEADER_FILE}" HEADER_FILE_FWD) - file(APPEND "${TEMP_CPP_FILE}" "#include \"${HEADER_FILE_FWD}\"\n") - endforeach() - - add_library(mrdocs-documentation-build STATIC "${TEMP_CPP_FILE}") - target_compile_features(mrdocs-documentation-build PRIVATE cxx_std_23) - target_include_directories(mrdocs-documentation-build - PRIVATE - "${SRC_INCLUDE_DIR}" - "${BIN_INCLUDE_DIR}" - # Public headers reference the libs/ libraries; libs/ is not added in - # this mode, so point at their include dirs directly. - "${PROJECT_SOURCE_DIR}/libs/polyfill/include" - "${PROJECT_SOURCE_DIR}/libs/dom/include" - "${PROJECT_SOURCE_DIR}/libs/handlebars/include" - ) - target_compile_definitions(mrdocs-documentation-build PRIVATE MRDOCS_STATIC_LINK) - return() -endif() - -#------------------------------------------------- -# Dependencies (only mrdocs-core needs these) -#------------------------------------------------- -# Found here, not in the root, because the library is their only direct -# consumer. They are attached to mrdocs-core PRIVATE (see the target settings -# below): implementation details, not usage requirements, so their include -# dirs and definitions never reach any consumer. The tool and tests reach into -# MrDocs's private headers too, so they link the same mrdocs-llvm interface -# target independently, not through mrdocs-core. set(CMAKE_FOLDER Dependencies) - -# LLVM + Clang. CMP0074 makes find_package search _ROOT env vars, but the -# validation and Clang_ROOT derivation want the CMake variable set explicitly. -if (NOT LLVM_ROOT AND DEFINED ENV{LLVM_ROOT}) - set(LLVM_ROOT "$ENV{LLVM_ROOT}") -endif() -if (LLVM_ROOT) - get_filename_component(LLVM_ROOT "${LLVM_ROOT}" ABSOLUTE) - set(LLVM_ROOT "${LLVM_ROOT}" CACHE PATH "Root of LLVM install." FORCE) - if (NOT EXISTS "${LLVM_ROOT}") - message(FATAL_ERROR "LLVM_ROOT (${LLVM_ROOT}) provided does not exist.") - endif() - if (NOT EXISTS "${LLVM_ROOT}/lib/cmake/llvm") - message(FATAL_ERROR "LLVM_ROOT (${LLVM_ROOT}) is invalid: no /lib/cmake/llvm.") - endif() - message(STATUS "LLVM_ROOT: ${LLVM_ROOT}") -endif() -if (Clang_ROOT) - get_filename_component(Clang_ROOT "${Clang_ROOT}" ABSOLUTE) - set(LLVM_ROOT "${LLVM_ROOT}" CACHE PATH "Root of Clang install." FORCE) -elseif (LLVM_ROOT) - set(Clang_ROOT "${LLVM_ROOT}" CACHE PATH "Root of Clang install." FORCE) -endif() - -# Modern usage: find_package(... CONFIG) exposes imported targets. Clang gives -# per-component targets (clangTooling, clangAST, ...) that already carry the -# LLVM+Clang include dirs transitively. LLVM itself still publishes headers and -# definitions only as variables (LLVM_INCLUDE_DIRS, LLVM_DEFINITIONS): there is -# no single interface target for them in this release, so we bundle everything -# into our own mrdocs-llvm interface target below. -find_package(LLVM REQUIRED CONFIG) -find_package(Clang REQUIRED CONFIG) - -set(LIBCXX_DIR "${LLVM_INCLUDE_DIR}/c++/v1" CACHE PATH "Path to libc++ include directory") -message(STATUS "LIBCXX_DIR: ${LIBCXX_DIR}") -if (NOT EXISTS "${LIBCXX_DIR}") - message(FATAL_ERROR "LIBCXX_DIR (${LIBCXX_DIR}) does not exist. Provide an LLVM with libc++ enabled.") -endif() - -list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") -include(HandleLLVMOptions) -string(REGEX REPLACE " /W[0-4]" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") -string(REGEX REPLACE " /W[0-4]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - +include(setup-llvm.cmake) find_package(jerryscript REQUIRED CONFIG) find_package(Lua CONFIG REQUIRED) - -# LLVM/Clang usage requirements, bundled once as an interface target right -# after the dependency is found. LLVM publishes its headers and definitions -# only as variables (no interface target in this release), so we wrap them -# here. Both mrdocs-core and, independently, the in-tree tool and tests that -# reach into private headers pulling in LLVM link this single target. -set_ternary(MRDOCS_CLANG_LIBS CLANG_SIMPLE_LIBS - "LLVM;clang;clang-cpp" - "clangAST;clangBasic;clangFrontend;clangIndex;clangTooling;clangToolingCore;clangToolingInclusions") -add_library(mrdocs-llvm INTERFACE) -target_include_directories(mrdocs-llvm SYSTEM INTERFACE - "${LLVM_INCLUDE_DIRS}" - "${CLANG_INCLUDE_DIRS}") -target_compile_definitions(mrdocs-llvm INTERFACE ${LLVM_DEFINITIONS}) -target_link_libraries(mrdocs-llvm INTERFACE ${MRDOCS_CLANG_LIBS}) - unset(CMAKE_FOLDER) #------------------------------------------------- -# mrdocs-core +# Glob sources #------------------------------------------------- +set(MRDOCS_GENERATED_HEADERS + ${PROJECT_BINARY_DIR}/include/mrdocs/Version.hpp + ${PROJECT_BINARY_DIR}/include/mrdocs/ConfigSchema.hpp) +set_source_files_properties(${MRDOCS_GENERATED_HEADERS} PROPERTIES GENERATED TRUE) file( GLOB_RECURSE LIB_SOURCES CONFIGURE_DEPENDS # Public @@ -139,20 +37,16 @@ file( ${CMAKE_CURRENT_SOURCE_DIR}/mrdocs/*.natvis # Natvis ${PROJECT_SOURCE_DIR}/include/*.natvis + # Generated + ${MRDOCS_GENERATED_HEADERS} ) -list(APPEND LIB_SOURCES - ${PROJECT_BINARY_DIR}/include/mrdocs/Version.hpp - ${PROJECT_BINARY_DIR}/include/mrdocs/ConfigSchema.hpp -) -# These are produced at build time by the mrdocs-codegen target (utils/codegen). -# The GENERATED property set there is directory-scoped (CMP0118 is OLD at our -# cmake_minimum_required 3.13), so it is invisible here; mark them GENERATED in -# this scope or add_library rejects them as missing sources on a clean configure. -set_source_files_properties( - ${PROJECT_BINARY_DIR}/include/mrdocs/Version.hpp - ${PROJECT_BINARY_DIR}/include/mrdocs/ConfigSchema.hpp - PROPERTIES GENERATED TRUE) + +#------------------------------------------------- +# Create target +#------------------------------------------------- add_library(mrdocs-core ${LIB_SOURCES}) +add_library(mrdocs::mrdocs-core ALIAS mrdocs-core) +add_dependencies(mrdocs-core mrdocs-codegen) target_compile_features(mrdocs-core PUBLIC cxx_std_23) target_include_directories(mrdocs-core PUBLIC @@ -161,118 +55,65 @@ target_include_directories(mrdocs-core "$" PRIVATE "${PROJECT_SOURCE_DIR}/src" - "${PROJECT_BINARY_DIR}/src" ) target_compile_definitions( mrdocs-core PUBLIC - # Only the link mode is part of the public API (it drives MRDOCS_DECL). ${MRDOCS_LINK_MODE_DEFINITION} + $<$:_WIN32_WINNT=0x0601> + $<$:_CRT_SECURE_NO_WARNINGS> + $<$:_SILENCE_CXX20_CISO646_REMOVED_WARNING> PRIVATE -DMRDOCS_TOOL ) +target_compile_options( + mrdocs-core + PUBLIC + $<$:/Zc:__cplusplus> # Platform.hpp + $<$:/Zc:preprocessor> # Describe.hpp uses __VA_OPT__ + $<$:-Wno-unused-private-field> + $<$:-Wno-unused-value> + PRIVATE + $<$:/permissive- /W4 /MP> + $<$,$>:/arch:AMD64> + $<$,$>:/Oy-> # Disable frame pointer omission + $<$:-Wno-covered-switch-default> +) -# Public library dependencies: the DOM, polyfills, and handlebars ARE part of -# MrDocs's public API (its public headers include , -# , etc.), so they propagate to consumers. -target_link_libraries(mrdocs-core PUBLIC mrdocs::polyfill) -target_link_libraries(mrdocs-core PUBLIC mrdocs::dom) -target_link_libraries(mrdocs-core PUBLIC mrdocs::handlebars) - -# LLVM/Clang, JerryScript, and Lua are implementation details, not part of the -# public API, so mrdocs-core depends on them PRIVATE and consumers of the -# public API never see them. LLVM/Clang come in through mrdocs-llvm, wrapped in -# $ so the link is build-tree only: mrdocs-core is an -# installed static library, and a plain PRIVATE link would place mrdocs-llvm in -# its exported link interface ($) and force it into the install -# export. The wrap keeps LLVM out of the installed interface while the wiring -# stays defined once, on mrdocs-llvm. -target_link_libraries(mrdocs-core PRIVATE $) +# Args for in-tree invocations of the mrdocs +set(MRDOCS_BUILTIN_DIR_ARGS + "--addons=${CMAKE_SOURCE_DIR}/data/mrdocs/addons" + "--stdlib-includes=${LIBCXX_DIR}" + "--libc-includes=${CMAKE_SOURCE_DIR}/data/mrdocs/headers/libc-stubs" + "--clang-resource-dir=${CLANG_RESOURCE_DIR}" + CACHE INTERNAL "Built-in directory flags for in-tree mrdocs invocations") -# When mrdocs-core is installed as a static archive (the default), it still -# contains unresolved references to LLVM/Clang. A downstream target linking the -# installed mrdocs::mrdocs-core must therefore have LLVM/Clang on its own link -# line to resolve them, even though they are private (not part of the public -# API). The $ link above is build-tree only, so it does not -# carry into the install export; re-expose the same libraries through the -# INSTALL interface. mrdocs-config.cmake.in runs find_dependency(LLVM) and -# find_dependency(Clang), so these targets are available when a consumer -# imports the package. develop linked `LLVM clang clang-cpp` PUBLIC, which had -# the same effect for installed consumers; this keeps that while still keeping -# LLVM out of the build-tree public interface. A shared mrdocs-core absorbs -# these symbols, so consumers of a shared build do not need them. -if (NOT MRDOCS_BUILD_SHARED) - target_link_libraries(mrdocs-core INTERFACE $) +#------------------------------------------------- +# Dependency linking +#------------------------------------------------- +# Public dependencies: linked by name and propagated to consumers +target_link_libraries(mrdocs-core PUBLIC mrdocs::polyfill mrdocs::dom mrdocs::handlebars) + +# Private dependencies +set(MRDOCS_PRIVATE_DEPENDENCIES mrdocs-llvm jerryscript::jerry-core jerryscript::jerry-port Lua::lua) +if (NOT MRDOCS_DO_REEXPORT_DEPENDENCIES) + # Link at build time only. It’ll be bundled when installed. + target_link_libraries(mrdocs-core PRIVATE $) +else () + # Linked by name and propagated to consumers, who will find_dependency() them + target_link_libraries(mrdocs-core PRIVATE ${MRDOCS_PRIVATE_DEPENDENCIES}) + install(TARGETS mrdocs-llvm EXPORT mrdocs-targets) endif () -target_include_directories(mrdocs-core SYSTEM PRIVATE - "${JERRYSCRIPT_INCLUDE_DIRS}" - "${jerryscript_ROOT}/include" - "/usr/local/include") -target_link_libraries(mrdocs-core PRIVATE jerryscript::jerry-core jerryscript::jerry-port) -target_link_libraries(mrdocs-core PRIVATE Lua::lua) - -# Windows, Win64 -if (WIN32) - target_compile_definitions( - mrdocs-core - PUBLIC - -D_WIN32_WINNT=0x0601 - -D_CRT_SECURE_NO_WARNINGS - -D_SILENCE_CXX20_CISO646_REMOVED_WARNING - ) - if(MSVC) - get_target_property(LLVM_CONFIGURATION_TYPE LLVMCore IMPORTED_CONFIGURATIONS) - if (LLVM_CONFIGURATION_TYPE STREQUAL RELWITHDEBINFO) - # Handle Debug/RelWithDebInfo mismatch between mrdocs and LLVM - target_compile_definitions(mrdocs-core PUBLIC $<$:-D_ITERATOR_DEBUG_LEVEL=0>) - target_compile_options(mrdocs-core PUBLIC $<$:/MD>) - endif() - if("${CMAKE_GENERATOR_PLATFORM}" STREQUAL "Win64") # 64-bit - target_compile_options(mrdocs-core PUBLIC /arch:AMD64) - endif() - target_compile_options( - mrdocs-core - PUBLIC - /permissive- # strict C++ - /W4 # enable all warnings - /MP # multi-processor compilation - /EHs # C++ Exception handling - $<$:/Oy-> # Disable frame pointer omission - ) +# You typically should not be doing it, but if someone attempts to link LLVM in MSVC/Release +# with MrDocs in MSVC/Debug, adjust flags to avoid a mismatch and propagate them to consumers. +if(MSVC) + get_target_property(LLVM_CONFIGURATION_TYPE LLVMCore IMPORTED_CONFIGURATIONS) + if (LLVM_CONFIGURATION_TYPE STREQUAL RELWITHDEBINFO) + # Handle Debug/RelWithDebInfo mismatch between mrdocs and LLVM + target_compile_definitions(mrdocs-core PUBLIC $<$:-D_ITERATOR_DEBUG_LEVEL=0>) + target_compile_options(mrdocs-core PUBLIC $<$:/MD>) endif() -endif () - -if (MRDOCS_CLANG) - target_compile_options( - mrdocs-core - PUBLIC - -Wno-unused-private-field - -Wno-unused-value - PRIVATE - -Wno-covered-switch-default - ) -endif () - -# The generated ConfigSchema/Version headers come from utils/codegen; depend on -# its target so generation is ordered before this library builds. -if (TARGET mrdocs-codegen) - add_dependencies(mrdocs-core mrdocs-codegen) -endif () - -#------------------------------------------------- -# Clang resource directory (build tree) -#------------------------------------------------- -# Replicate the clang resource directory inside our build so libclang finds it -# when running mrdocs directly from build/bin. Needs LLVM_BINARY_DIR, so it -# lives here with the LLVM dependency. -file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/lib/clang") -set(RESOURCE_DIR "lib/clang/${Clang_VERSION_MAJOR}") -file(CREATE_LINK "${LLVM_BINARY_DIR}/${RESOURCE_DIR}" "${PROJECT_BINARY_DIR}/${RESOURCE_DIR}" SYMBOLIC) -if (NOT EXISTS "${LLVM_BINARY_DIR}/${RESOURCE_DIR}/include") - message(FATAL_ERROR - "Clang resource headers (${LLVM_BINARY_DIR}/${RESOURCE_DIR}/include) do not exist.\n" - "Please provide an LLVM install that contains the clang resource directory.\n") endif() #------------------------------------------------- @@ -301,7 +142,94 @@ if (MRDOCS_INSTALL) install(DIRECTORY ${LIBCXX_DIR}/ DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/mrdocs/headers/libcxx FILES_MATCHING PATTERN "*") - install(DIRECTORY ${LLVM_BINARY_DIR}/${RESOURCE_DIR}/include - DESTINATION ${RESOURCE_DIR} + # Clang's builtin headers, bundled like the libcxx/libc-stubs headers. mrdocs + # points -resource-dir here (the clang-resource-dir option) and clang appends + # /include, so the headers install under headers/clang/include. + install(DIRECTORY ${CLANG_RESOURCE_DIR}/include + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/mrdocs/headers/clang FILES_MATCHING PATTERN "*") endif() + +#------------------------------------------------- +# Bundle private dependencies into mrdocs-core +#------------------------------------------------- +# Bundle mrdocs-core library with the private libraries it links +# into one self-contained fat library, so a consumer project can +# link the static mrdocs-core without the dependencies installed. +# This lets a plugin author link against the exact LLVM mrdocs +# was built with without building mrdocs from source. +if (MRDOCS_DO_BUNDLE_DEPENDENCIES AND MRDOCS_INSTALL) + set(_mrdocs_bundled_lib_filename "${CMAKE_STATIC_LIBRARY_PREFIX}mrdocs-core-bundled${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(_mrdocs_bundled_lib_path "${CMAKE_CURRENT_BINARY_DIR}/${_mrdocs_bundled_lib_filename}") + + # Find all transitive static dependencies of mrdocs-core and the system libraries they use + mrdocs_collect_static_libs( + _mrdocs_bundle_deps # The static libraries mrdocs-core references, recursively + _mrdocs_bundle_system_libs # The system libraries those static libraries reference, recursively + ${MRDOCS_PRIVATE_DEPENDENCIES}) + + if (WIN32) + # Windows: lib.exe only concatenates whole .lib files. + # There is no -r / no member-level pruning on COFF), so instead we only merge + # only the libraries mrdocs actually *references* to make this smaller. + # Only the referenced static libraries, so lib.exe does not embed all of LLVM. + if (CMAKE_AR) + set(_mrdocs_bundled_lib_path_tool "${CMAKE_AR}") + else() + find_program(_mrdocs_bundled_lib_path_tool NAMES lib.exe lib llvm-lib REQUIRED) + endif() + add_custom_command( + OUTPUT ${_mrdocs_bundled_lib_path} + DEPENDS mrdocs-core + COMMAND ${CMAKE_COMMAND} -E rm -f ${_mrdocs_bundled_lib_path} + COMMAND ${_mrdocs_bundled_lib_path_tool} /NOLOGO "/OUT:${_mrdocs_bundled_lib_path}" + $ ${_mrdocs_bundle_deps} + COMMENT "Bundling mrdocs-core with its referenced private toolchain" + VERBATIM COMMAND_EXPAND_LISTS) + else() + # Unix: a relocatable link (ld -r) force-loads mrdocs-core and member-selects + # from the collected dependency archives, so only the members mrdocs actually + # reaches survive. The code it never calls is dropped, then re-archived. This + # is the same list Windows merges whole; ld -r just prunes it to what is used. + set(_mrdocs_bundled_obj_path "${CMAKE_CURRENT_BINARY_DIR}/mrdocs-core-bundled${CMAKE_C_OUTPUT_EXTENSION}") + # mrdocs-core is force-loaded whole (it holds the roots). GNU ld resolves + # archives in a single pass, so wrap the rest in --start-group/--end-group + # to resolve the cross-archive references in Clang's vtables regardless of + # order; Apple ld64 is multi-pass and rejects that flag. + if (APPLE) + set(_bundle_inputs + -Wl,-force_load,$ + ${_mrdocs_bundle_deps}) + else() + set(_bundle_inputs + -Wl,--whole-archive $ -Wl,--no-whole-archive + -Wl,--start-group + ${_mrdocs_bundle_deps} + -Wl,--end-group) + endif() + add_custom_command( + OUTPUT ${_mrdocs_bundled_lib_path} + DEPENDS mrdocs-core + COMMAND ${CMAKE_CXX_COMPILER} -r -nostdlib -o ${_mrdocs_bundled_obj_path} ${_bundle_inputs} + COMMAND ${CMAKE_COMMAND} -E rm -f ${_mrdocs_bundled_lib_path} + COMMAND ${CMAKE_AR} qcs ${_mrdocs_bundled_lib_path} ${_mrdocs_bundled_obj_path} + COMMENT "Bundling mrdocs-core with its pruned private toolchain" + VERBATIM COMMAND_EXPAND_LISTS) + endif() + + add_custom_target(mrdocs-core-bundle ALL DEPENDS ${_mrdocs_bundled_lib_path}) + + # Replace the thin libmrdocs-core installed above with the fat one, same path. + # The mrdocs-targets export keeps naming it. + install(FILES ${_mrdocs_bundled_lib_path} + DESTINATION ${CMAKE_INSTALL_LIBDIR} + RENAME ${CMAKE_STATIC_LIBRARY_PREFIX}mrdocs-core${CMAKE_STATIC_LIBRARY_SUFFIX} + COMPONENT development) + + # The fat archive embeds the LLVM/Clang objects but not the OS libraries they + # reference. These are still linked to the installed target. + if (_mrdocs_bundle_system_libs) + target_link_libraries(mrdocs-core INTERFACE "$") + endif () +endif() + diff --git a/src/mrdocs/AST/ASTVisitor.cpp b/src/mrdocs/AST/ASTVisitor.cpp index c47f94e2d73..3b42726e132 100644 --- a/src/mrdocs/AST/ASTVisitor.cpp +++ b/src/mrdocs/AST/ASTVisitor.cpp @@ -1567,7 +1567,7 @@ populate( for (std::size_t i = 0; i < TPL->size(); ++i) { auto& Param = Result->Params.emplace_back(std::in_place_type); - populate(Param, TPL->getParam(i)); + populate(Param, TPL->getParam(static_cast(i))); } if (TTPD->hasDefaultArgument() && !Result->Default) { @@ -1617,7 +1617,7 @@ populate( std::size_t i = 0; while (explicitIt != ExplicitTemplateParameters.end()) { - clang::NamedDecl const* P = TPL->getParam(i); + clang::NamedDecl const* P = TPL->getParam(static_cast(i)); Polymorphic& Param = i < TI.Params.size() ? TI.Params[i] : @@ -2621,7 +2621,7 @@ extractSFINAEInfo(clang::QualType const T) for (std::size_t I = 0; I < Args.size(); ++I) { if (I < SFINAEControl->ControllingParams.size() - && SFINAEControl->ControllingParams[I]) + && SFINAEControl->ControllingParams[static_cast(I)]) { MRDOCS_SYMBOL_TRACE(Args[I], context_); clang::TemplateArgument ArgsI = Args[I]; @@ -2694,7 +2694,7 @@ getSFINAEControlParams( // Find the index of the parameter that represents the SFINAE result // in the primary template arguments - unsigned ParamIdx = FindParam(ATD->getInjectedTemplateArgs(context_), *resultType); + unsigned ParamIdx = static_cast(FindParam(ATD->getInjectedTemplateArgs(context_), *resultType)); // Return the controlling parameters with values corresponding to // the primary template arguments @@ -2706,28 +2706,28 @@ getSFINAEControlParams( for (std::size_t i = 0; i < sfinaeControl->ControllingParams.size(); ++i) { - if (sfinaeControl->ControllingParams[i]) + if (sfinaeControl->ControllingParams[static_cast(i)]) { // Find the index of the parameter that represents the SFINAE // result in the underlying template arguments - auto resultType = tryGetTemplateArgument( + auto argResultType = tryGetTemplateArgument( sfinaeControl->Parameters, underlyingTemplateInfo->Arguments, i); - MRDOCS_CHECK_OR_CONTINUE(resultType); - MRDOCS_SYMBOL_TRACE(*resultType, context_); + MRDOCS_CHECK_OR_CONTINUE(argResultType); + MRDOCS_SYMBOL_TRACE(*argResultType, context_); // Find the index of the parameter that represents the param // in the primary template arguments - auto ParamIdx = FindParam( + auto argParamIdx = FindParam( ATD->getInjectedTemplateArgs(context_), - *resultType); - if (ParamIdx == static_cast(-1)) + *argResultType); + if (argParamIdx == static_cast(-1)) { continue; } - primaryControllingParams.set(ParamIdx); + primaryControllingParams.set(static_cast(argParamIdx)); } } @@ -2753,7 +2753,7 @@ getSFINAEControlParams( // For instance, in the specialization `std::enable_if::type`, // `type` is `T`, which corresponds to the second template parameter // `T`, so `ParamIdx` is `1` to represent the second parameter. - unsigned ParamIdx = -1; + unsigned ParamIdx = static_cast(-1); // The `IsMismatch` function checks if there's a mismatch between the // clang::CXXRecordDecl of the clang::ClassTemplateDecl and the specified template @@ -2883,7 +2883,7 @@ getSFINAEControlParams( // `type` is `T`, which corresponds to the second template // parameter `T`, so `ParamIdx` is `1` to represent the // second parameter. - ParamIdx = FoundIdx; + ParamIdx = static_cast(FoundIdx); // Get this primary template argument as a template // argument of the current type. clang::TemplateArgument MappedPrimary = PrimaryArgs[FoundIdx]; @@ -2940,7 +2940,7 @@ getSFINAEControlParams( // template parameters that control the SFINAE result. The controlling // parameters are expressions that cannot be converted to // non-type template parameters. - llvm::SmallBitVector ControllingParams(PrimaryArgs.size()); + llvm::SmallBitVector ControllingParams(static_cast(PrimaryArgs.size())); for(auto* CTPSD : PartialSpecs) { MRDOCS_SYMBOL_TRACE(CTPSD, context_); auto PartialArgs = CTPSD->getTemplateArgs().asArray(); @@ -2965,7 +2965,7 @@ getSFINAEControlParams( default: continue; } - ControllingParams.set(i); + ControllingParams.set(static_cast(i)); } } @@ -3031,7 +3031,7 @@ tryGetTemplateArgument( MRDOCS_CHECK_OR(Index < Parameters->size(), std::nullopt); // Attempt to get the default argument of the template parameter - clang::NamedDecl* ND = Parameters->getParam(Index); + clang::NamedDecl* ND = Parameters->getParam(static_cast(Index)); MRDOCS_SYMBOL_TRACE(ND, context_); if(auto* TTPD = dyn_cast(ND); TTPD && TTPD->hasDefaultArgument()) diff --git a/src/mrdocs/AST/MrDocsFileSystem.hpp b/src/mrdocs/AST/MrDocsFileSystem.hpp index c55883876bc..1a7c42bab74 100644 --- a/src/mrdocs/AST/MrDocsFileSystem.hpp +++ b/src/mrdocs/AST/MrDocsFileSystem.hpp @@ -98,7 +98,7 @@ class MrDocsFileSystem : public llvm::vfs::FileSystem { shim_macro += '_'; } else { - shim_macro += std::toupper(c); + shim_macro += static_cast(std::toupper(static_cast(c))); } } std::string result; diff --git a/src/mrdocs/Config/ReferenceDirectories.cpp b/src/mrdocs/Config/ReferenceDirectories.cpp new file mode 100644 index 00000000000..19740260068 --- /dev/null +++ b/src/mrdocs/Config/ReferenceDirectories.cpp @@ -0,0 +1,133 @@ +// +// 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) 2026 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#include +#include +#include +#include +#include +#include + +// The library-relative step (below) is only meaningful for a shared mrdocs-core, +// which is installed as its own file next to the executable's directory. For the +// static library there is no such file -- the code is fused into the consuming +// executable -- so it is excluded and only the executable location is used. +#if defined(MRDOCS_SHARED_LINK) +# if defined(_WIN32) +# include +# include +# else +# include +# endif +#endif + +namespace mrdocs { + +namespace { + +// Anchor whose address locates the module that provides mrdocs-core. +void mrdocsExecutableAnchor() {} + +#if defined(MRDOCS_SHARED_LINK) +// Path of the shared mrdocs-core library (the module that contains the anchor), +// or empty if it cannot be determined. +std::string +mrdocsLibraryPath() +{ +# if defined(_WIN32) + HMODULE hmod = nullptr; + if (!::GetModuleHandleExW( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS + | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(&mrdocsExecutableAnchor), + &hmod)) + { + return {}; + } + std::wstring buf(MAX_PATH, L'\0'); + for (;;) + { + DWORD const n = ::GetModuleFileNameW( + hmod, buf.data(), static_cast(buf.size())); + if (n == 0) + { + return {}; + } + if (n < buf.size()) + { + buf.resize(n); + break; + } + if (buf.size() > 65536) + { + return {}; + } + buf.resize(buf.size() * 2); + } + std::string utf8; + if (llvm::convertWideToUTF8(buf, utf8)) + { + return utf8; + } + return {}; +# else + Dl_info info{}; + if (::dladdr(reinterpret_cast(&mrdocsExecutableAnchor), &info) + && info.dli_fname) + { + return info.dli_fname; + } + return {}; +# endif +} +#endif // MRDOCS_SHARED_LINK + +} // (anon) + +ReferenceDirectories:: +ReferenceDirectories(std::string root) + : mrdocsRoot(std::move(root)) +{ + llvm::SmallVector buf; + if (!llvm::sys::fs::current_path(buf)) + { + cwd.assign(buf.data(), buf.size()); + } + + // Precedence for the MrDocs root: MRDOCS_ROOT in the environment, then the + // compile-time default passed as `root` (empty in MrDocs's own build; the + // installed prefix for a downstream project via find_package), then a + // location two levels below the file that provides mrdocs-core -- the shared + // library when built shared (so a shared consumer resolves MrDocs's own + // install), otherwise the running executable. + if (char const* env = std::getenv("MRDOCS_ROOT"); env && *env) + { + mrdocsRoot = env; + } + else if (mrdocsRoot.empty()) + { + std::string self; +#if defined(MRDOCS_SHARED_LINK) + self = mrdocsLibraryPath(); +#endif + if (self.empty()) + { + self = llvm::sys::fs::getMainExecutable( + nullptr, reinterpret_cast(&mrdocsExecutableAnchor)); + } + if (!self.empty()) + { + mrdocsRoot = std::string(files::getParentDir(self, 2)); + } + } +} + +} // mrdocs diff --git a/src/mrdocs/ConfigOptions.json b/src/mrdocs/ConfigOptions.json index 50b1e88d41e..eb5d5b46394 100644 --- a/src/mrdocs/ConfigOptions.json +++ b/src/mrdocs/ConfigOptions.json @@ -80,18 +80,6 @@ "type": "bool", "default": true }, - { - "name": "stdlib-includes", - "brief": "C++ Standard Library include paths", - "details": "When `use-system-stdlib` is disabled, the C++ standard library headers are available in these paths.", - "type": "list", - "default": [ - "/share/mrdocs/headers/libcxx" - ], - "relative-to": "", - "must-exist": false, - "should-exist": true - }, { "name": "use-system-libc", "brief": "Use the system C standard library", @@ -99,18 +87,6 @@ "type": "bool", "default": true }, - { - "name": "libc-includes", - "brief": "Standard Library include paths", - "details": "When `use-system-libc` is disabled, the C standard library headers are available in these paths.", - "type": "list", - "default": [ - "/share/mrdocs/headers/libc-stubs" - ], - "relative-to": "", - "must-exist": false, - "should-exist": true - }, { "name": "system-includes", "brief": "System include paths", @@ -505,15 +481,6 @@ "type": "string", "default": "" }, - { - "name": "addons", - "brief": "Path to the Addons directory", - "details": "The directory of template files the generators use to build the documentation. Leave it unset to use the defaults that ship at `share/mrdocs/addons` in the MrDocs installation. To customize the output, copy those defaults to your own directory and point this option at it.", - "type": "path", - "default": "/share/mrdocs/addons", - "relative-to": "", - "must-exist": true - }, { "name": "addons-supplemental", "brief": "Additional addons layered on top of the base addons", @@ -735,5 +702,55 @@ "default": false } ] + }, + { + "category": "Built-in directories", + "brief": "Where MrDocs finds its own bundled resources", + "details": "These point at the addons and the parse-time header sets that MrDocs ships with. They default to `/share/mrdocs/...`, derived from the MrDocs root, and are not meant for everyday configuration. They are exposed so tests and unusual installs can redirect MrDocs's built-in directories.", + "options": [ + { + "name": "addons", + "brief": "Path to the Addons directory", + "details": "The directory of template files the generators use to build the documentation. Leave it unset to use the defaults that ship at `share/mrdocs/addons` in the MrDocs installation. To customize the output, copy those defaults to your own directory and point this option at it.", + "type": "path", + "default": "/share/mrdocs/addons", + "relative-to": "", + "must-exist": true + }, + { + "name": "stdlib-includes", + "brief": "C++ Standard Library include paths", + "details": "When `use-system-stdlib` is disabled, the C++ standard library headers are available in these paths.", + "type": "list", + "default": [ + "/share/mrdocs/headers/libcxx" + ], + "relative-to": "", + "must-exist": false, + "should-exist": true + }, + { + "name": "libc-includes", + "brief": "Standard Library include paths", + "details": "When `use-system-libc` is disabled, the C standard library headers are available in these paths.", + "type": "list", + "default": [ + "/share/mrdocs/headers/libc-stubs" + ], + "relative-to": "", + "must-exist": false, + "should-exist": true + }, + { + "name": "clang-resource-dir", + "brief": "Clang resource directory", + "details": "Directory holding Clang's builtin headers (`stddef.h`, `stdarg.h`, and the other compiler intrinsics) in its `include/` subdirectory. MrDocs bundles the resource directory that matches its embedded Clang and passes it as `-resource-dir`, so parsing does not depend on a Clang installation on the host. Leave empty to let Clang locate its own resource directory.", + "type": "path", + "default": "/share/mrdocs/headers/clang", + "relative-to": "", + "must-exist": false, + "should-exist": true + } + ] } ] diff --git a/src/mrdocs/Corpus.cpp b/src/mrdocs/Corpus.cpp index f4d62564a4b..94195a9ceca 100644 --- a/src/mrdocs/Corpus.cpp +++ b/src/mrdocs/Corpus.cpp @@ -74,7 +74,7 @@ findFirstParentInfo( MRDOCS_CHECK_OR(contextUniquePtr, SymbolID::invalid); auto& context = *contextUniquePtr; bool const isParent = visit(context, []( - InfoTy const& I) -> bool + InfoTy const&) -> bool { return SymbolParent; }); diff --git a/src/mrdocs/Engines/Lua.cpp b/src/mrdocs/Engines/Lua.cpp index 5e578cb439e..785de544bc5 100644 --- a/src/mrdocs/Engines/Lua.cpp +++ b/src/mrdocs/Engines/Lua.cpp @@ -272,7 +272,7 @@ static void luaM_report( Error const& err, - source_location loc = + [[maybe_unused]] source_location loc = source_location::current()) { SourceLocation Loc(err.location()); @@ -799,7 +799,7 @@ domValue_push( static char const* Reader( - lua_State *L, + lua_State *, void* data, size_t* size) { diff --git a/src/mrdocs/Engines/Lua/Scope.ipp b/src/mrdocs/Engines/Lua/Scope.ipp index 7113dad42dc..f25e814d118 100644 --- a/src/mrdocs/Engines/Lua/Scope.ipp +++ b/src/mrdocs/Engines/Lua/Scope.ipp @@ -72,7 +72,7 @@ Expected Scope:: getGlobal( std::string_view key, - source_location loc) + [[maybe_unused]] source_location loc) { Access A(*this); lua_pushglobaltable(A); diff --git a/src/mrdocs/Generators/noop/NoopGenerator.cpp b/src/mrdocs/Generators/noop/NoopGenerator.cpp index 2f916a6104b..2d8a19d900e 100644 --- a/src/mrdocs/Generators/noop/NoopGenerator.cpp +++ b/src/mrdocs/Generators/noop/NoopGenerator.cpp @@ -15,7 +15,7 @@ namespace noop { Expected NoopGenerator:: -build(Corpus const&, Config const& config) const +build(Corpus const&, Config const&) const { // Extraction has already happened by the time a generator runs; // the no-op generator deliberately writes nothing. diff --git a/src/mrdocs/MrDocsCompilationDatabase.cpp b/src/mrdocs/MrDocsCompilationDatabase.cpp index 4375c5f16f4..148fdfeb9af 100644 --- a/src/mrdocs/MrDocsCompilationDatabase.cpp +++ b/src/mrdocs/MrDocsCompilationDatabase.cpp @@ -421,6 +421,18 @@ adjustCommandLine( } } + // ------------------------------------------------------ + // Clang resource directory + // ------------------------------------------------------ + // Point clang at the bundled resource directory so its builtin headers + // (stddef.h, stdarg.h, ...) come from the embedded clang rather than the + // host. clang appends `/include` to it. Empty lets clang locate its own. + if (!config.clangResourceDir.empty()) + { + new_cmdline.emplace_back("-resource-dir"); + new_cmdline.emplace_back(config.clangResourceDir); + } + // ------------------------------------------------------ // Add user directories to include search path // ------------------------------------------------------ diff --git a/src/setup-llvm.cmake b/src/setup-llvm.cmake new file mode 100644 index 00000000000..e178bbe419e --- /dev/null +++ b/src/setup-llvm.cmake @@ -0,0 +1,103 @@ +# +# 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) 2026 Alan de Freitas (alandefreitas@gmail.com) +# +# Official repository: https://github.com/cppalliance/mrdocs +# + +# +# Find LLVM + Clang and create a wrapped mrdocs-llvm interface target. +# +include_guard(GLOBAL) + +#------------------------------------------------- +# Ensure directories +#------------------------------------------------- +# Ensure _ROOT variables and give us a strong fatal error +# with a better explanation if they're missing. MrDocs requires an +# explicit path with our specific build. We never use an LLVM installation +# from the system. +if (NOT LLVM_ROOT AND DEFINED ENV{LLVM_ROOT}) + set(LLVM_ROOT "$ENV{LLVM_ROOT}") +endif() +if (LLVM_ROOT) + get_filename_component(LLVM_ROOT "${LLVM_ROOT}" ABSOLUTE) + set(LLVM_ROOT "${LLVM_ROOT}" CACHE PATH "Root of LLVM install." FORCE) + if (NOT EXISTS "${LLVM_ROOT}") + message(FATAL_ERROR "LLVM_ROOT (${LLVM_ROOT}) provided does not exist.") + endif() + if (NOT EXISTS "${LLVM_ROOT}/lib/cmake/llvm") + message(FATAL_ERROR "LLVM_ROOT (${LLVM_ROOT}) is invalid: no /lib/cmake/llvm.") + endif() + message(STATUS "LLVM_ROOT: ${LLVM_ROOT}") +endif() +if (Clang_ROOT) + get_filename_component(Clang_ROOT "${Clang_ROOT}" ABSOLUTE) + set(LLVM_ROOT "${LLVM_ROOT}" CACHE PATH "Root of Clang install." FORCE) +elseif (LLVM_ROOT) + set(Clang_ROOT "${LLVM_ROOT}" CACHE PATH "Root of Clang install." FORCE) +endif() + +#------------------------------------------------- +# Find packages +#------------------------------------------------- +# LLVM publishes headers and definitions as variables. +# There is no single interface target for them. +find_package(LLVM REQUIRED CONFIG) +# Clang gives per-component targets (clangTooling, clangAST, ...) that +# link LLVM libraries transitively but not the headers or definitions. +find_package(Clang REQUIRED CONFIG) +# Find libc++ headers, which are not part of the LLVM/Clang CMake packages. The +# include dir is always /include/c++/v1. +set(LIBCXX_DIR "${LLVM_INCLUDE_DIR}/c++/v1" CACHE PATH "Path to libc++ include directory") +message(STATUS "LIBCXX_DIR: ${LIBCXX_DIR}") +if (NOT EXISTS "${LIBCXX_DIR}") + message(FATAL_ERROR "LIBCXX_DIR (${LIBCXX_DIR}) does not exist. Provide an LLVM with libc++ enabled.") +endif() +# Clang's resource directory: builtin headers (stddef.h, stdarg.h, ...) LLVM ships +# at /lib/clang/. +set(CLANG_RESOURCE_DIR "${LLVM_BINARY_DIR}/lib/clang/${Clang_VERSION_MAJOR}" + CACHE PATH "Path to clang's resource directory (builtin headers under include/)") +message(STATUS "CLANG_RESOURCE_DIR: ${CLANG_RESOURCE_DIR}") +if (NOT EXISTS "${CLANG_RESOURCE_DIR}/include") + message(FATAL_ERROR "CLANG_RESOURCE_DIR (${CLANG_RESOURCE_DIR}) has no include/ subdirectory. Provide an LLVM install that contains the clang resource directory.") +endif() + +#------------------------------------------------- +# Replay flags +#------------------------------------------------- +# Replay the flags LLVM was built with +# LLVM doesn't expose modular targets +list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") +include(HandleLLVMOptions) +# HandleLLVMOptions forces a /W level and a set of -wd suppressions into the global +# flags. Strip both: mrdocs-core sets its own /W4, and LLVM's header warnings are +# scoped to LLVM headers via /external:W0 on mrdocs-llvm below -- NOT suppressed +# project-wide, so real warnings in mrdocs's own code are never hidden. +if (MSVC) + string(REGEX REPLACE " /W[0-4]" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") + string(REGEX REPLACE " /W[0-4]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + string(REGEX REPLACE " [-/]wd[0-9]+" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") + string(REGEX REPLACE " [-/]wd[0-9]+" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") +endif() + +#------------------------------------------------- +# Create interface target +#------------------------------------------------- +# Wrap LLVM/Clang's usage requirements into a modular interface target. +add_library(mrdocs-llvm INTERFACE) +target_include_directories(mrdocs-llvm SYSTEM INTERFACE + "$" + "$") +target_compile_definitions(mrdocs-llvm INTERFACE ${LLVM_DEFINITIONS}) +target_link_libraries(mrdocs-llvm INTERFACE clangAST clangBasic clangFrontend clangIndex clangTooling clangToolingCore clangToolingInclusions) +if (MSVC) + # Mark LLVM warnings as external so they don't leak. + # A few of the warnings couldn't be scoped, so they need an explicit /wd. + # - 4701/4702/4703 are codegen warnings + # - 4244/4245/4267 are narrowing warnings from STL templates that end up in + target_compile_options(mrdocs-llvm INTERFACE /external:W0 /wd4701 /wd4702 /wd4703 /wd4244 /wd4245 /wd4267) +endif() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ae2b57ec8a6..33af7d5dd9b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,10 +8,6 @@ # Official repository: https://github.com/cppalliance/mrdocs # -if (MRDOCS_BUILD_HEADERS_ONLY OR NOT MRDOCS_BUILD_TESTS) - return() -endif() - # Each test suite is a separate executable and owns its own CMakeLists: # unit/ - public-API unit tests (mrdocs-unit-tests) # golden/ - reference-output harness (mrdocs-golden-tests) @@ -27,12 +23,8 @@ add_test(NAME mrdocs-self-doc "${CMAKE_SOURCE_DIR}/CMakeLists.txt" "--config=${CMAKE_SOURCE_DIR}/docs/mrdocs.yml" "--output=${MRDOCS_SELF_DOC_OUTPUT}" + ${MRDOCS_BUILTIN_DIR_ARGS} --generator=noop - "--addons=${CMAKE_SOURCE_DIR}/data/mrdocs/addons" - "--stdlib-includes=${LIBCXX_DIR}" - "--stdlib-includes=${STDLIB_INCLUDE_DIR}" - "--libc-includes=${CMAKE_SOURCE_DIR}/data/mrdocs/headers/libc-stubs" - --concurrency=16 --log-level=debug $<$:--warn-as-error=true> ) diff --git a/tests/cmake/CMakeLists.txt b/tests/cmake/CMakeLists.txt new file mode 100644 index 00000000000..e73e366363a --- /dev/null +++ b/tests/cmake/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# 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) 2026 Alan de Freitas (alandefreitas@gmail.com) +# +# Official repository: https://github.com/cppalliance/mrdocs +# + +cmake_minimum_required(VERSION 3.20) +project(mrdocs_cmake_consumer LANGUAGES CXX C) + +# Use mrdocs as a library and run it as a test. Discovery is driven by the +# exact-case mrdocs_ROOT (honored since CMake 3.12), so no policy opt-in is needed. +find_package(mrdocs REQUIRED) +add_executable(consumer src/main.cpp) +target_link_libraries(consumer PRIVATE mrdocs::mrdocs-core) +target_compile_features(consumer PRIVATE cxx_std_23) +enable_testing() +add_test(NAME consumer-runs COMMAND consumer) + +# A plain library, present so there is some project code to document. +add_library(example_calc src/calculator.cpp) +target_include_directories(example_calc PUBLIC include) +target_compile_features(example_calc PUBLIC cxx_std_20) +add_custom_target(consumer-docs ALL + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/reference + COMMAND ${MRDOCS_EXECUTABLE} + --config=${CMAKE_CURRENT_SOURCE_DIR}/mrdocs.yml + --compilation-database=${CMAKE_CURRENT_SOURCE_DIR}/docs/compile_commands.json + --output=${CMAKE_CURRENT_BINARY_DIR}/reference + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/include/example/calculator.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/mrdocs.yml + ${CMAKE_CURRENT_SOURCE_DIR}/docs/compile_commands.json + COMMENT "Generate consumer docs with MrDocs" + USES_TERMINAL) diff --git a/tests/cmake/docs/compile_commands.json b/tests/cmake/docs/compile_commands.json new file mode 100644 index 00000000000..70d0ec4a10b --- /dev/null +++ b/tests/cmake/docs/compile_commands.json @@ -0,0 +1,7 @@ +[ + { + "directory": "${MRDOCS_SOURCE_ROOT}", + "command": "clang++ -std=c++20 -I${MRDOCS_SOURCE_ROOT}/include -c ${MRDOCS_SOURCE_ROOT}/src/calculator.cpp -o ${MRDOCS_SOURCE_ROOT}/calculator.o", + "file": "${MRDOCS_SOURCE_ROOT}/src/calculator.cpp" + } +] diff --git a/tests/cmake/include/example/calculator.hpp b/tests/cmake/include/example/calculator.hpp new file mode 100644 index 00000000000..93abcbb97d8 --- /dev/null +++ b/tests/cmake/include/example/calculator.hpp @@ -0,0 +1,33 @@ +// +// 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) 2026 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#ifndef EXAMPLE_CALCULATOR_HPP +#define EXAMPLE_CALCULATOR_HPP + +namespace example { + +/** A tiny calculator. + + This type exists only to give the mrdocs CMake extension a documented + symbol to extract from this consumer project. +*/ +class Calculator +{ +public: + /// Return the sum of two integers. + int add(int a, int b) const; + + /// Return the product of two integers. + int multiply(int a, int b) const; +}; + +} // namespace example + +#endif diff --git a/tests/cmake/mrdocs.yml b/tests/cmake/mrdocs.yml new file mode 100644 index 00000000000..25740218aa9 --- /dev/null +++ b/tests/cmake/mrdocs.yml @@ -0,0 +1,11 @@ +# +# 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) 2026 Alan de Freitas (alandefreitas@gmail.com) +# +# Official repository: https://github.com/cppalliance/mrdocs +# + +source-root: . diff --git a/tests/cmake/src/calculator.cpp b/tests/cmake/src/calculator.cpp new file mode 100644 index 00000000000..e26184612a8 --- /dev/null +++ b/tests/cmake/src/calculator.cpp @@ -0,0 +1,27 @@ +// +// 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) 2026 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#include + +namespace example { + +int +Calculator::add(int a, int b) const +{ + return a + b; +} + +int +Calculator::multiply(int a, int b) const +{ + return a * b; +} + +} // namespace example diff --git a/tests/cmake/src/main.cpp b/tests/cmake/src/main.cpp new file mode 100644 index 00000000000..5e4ebd87a8e --- /dev/null +++ b/tests/cmake/src/main.cpp @@ -0,0 +1,51 @@ +// +// 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) 2026 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#include +#include +#include +#include + +int +main() +{ + // Exercises mrdocs and all its bundled private dependencies, forcing + // the linker to resolve those symbols out of the archive. + + // LLVM / Clang: mrdocs's configuration loader parses YAML with LLVM. + mrdocs::Config config; + if (!mrdocs::Config::load(config, "", nullptr)) + { + std::cerr << "Config::load failed\n"; + return 1; + } + + // JerryScript: compile and run a trivial script. + mrdocs::js::Context jsContext; + mrdocs::js::Scope jsScope(jsContext); + if (!jsScope.script("var x = 1 + 1;")) + { + std::cerr << "JavaScript engine failed\n"; + return 1; + } + + // Lua: compile a trivial chunk. + mrdocs::lua::Context luaContext; + mrdocs::lua::Scope luaScope(luaContext); + if (!luaScope.loadChunk("return 1 + 1")) + { + std::cerr << "Lua engine failed\n"; + return 1; + } + + std::cout << "mrdocs bundled dependencies (LLVM, Clang, JerryScript, Lua) " + "linked and ran from a consumer\n"; + return 0; +} diff --git a/tests/golden/CMakeLists.txt b/tests/golden/CMakeLists.txt index ff6fd54ed3c..dbfab8a935a 100644 --- a/tests/golden/CMakeLists.txt +++ b/tests/golden/CMakeLists.txt @@ -51,9 +51,7 @@ add_test(NAME mrdocs-golden-tests mrdocs-golden-tests --action=test "${CMAKE_CURRENT_SOURCE_DIR}/fixtures" - "--addons=${CMAKE_SOURCE_DIR}/data/mrdocs/addons" - "--stdlib-includes=${LIBCXX_DIR}" - "--libc-includes=${CMAKE_SOURCE_DIR}/data/mrdocs/headers/libc-stubs" + ${MRDOCS_BUILTIN_DIR_ARGS} --log-level=warn ) foreach (action IN ITEMS test create update) @@ -62,9 +60,7 @@ foreach (action IN ITEMS test create update) mrdocs-golden-tests --action=${action} "${CMAKE_CURRENT_SOURCE_DIR}/fixtures" - "--addons=${CMAKE_SOURCE_DIR}/data/mrdocs/addons" - "--stdlib-includes=${LIBCXX_DIR}" - "--libc-includes=${CMAKE_SOURCE_DIR}/data/mrdocs/headers/libc-stubs" + ${MRDOCS_BUILTIN_DIR_ARGS} --log-level=warn DEPENDS mrdocs-golden-tests ) @@ -96,12 +92,13 @@ endif() # where bash is unavailable (Windows). find_program(BASH_PROGRAM bash) if (BASH_PROGRAM AND NOT WIN32) - set(MRDOCS_SCHEMA_STDLIB "--stdlib-includes=${LIBCXX_DIR}") - if (STDLIB_INCLUDE_DIR) - string(APPEND MRDOCS_SCHEMA_STDLIB " --stdlib-includes=${STDLIB_INCLUDE_DIR}") - endif() + # generate-schema.sh passes --addons itself (ADDONS env); feed the remaining + # built-in dir flags through MRDOCS_EXTRA_ARGS as a space-separated string. + set(_schema_extra_args ${MRDOCS_BUILTIN_DIR_ARGS}) + list(FILTER _schema_extra_args EXCLUDE REGEX "^--addons=") + string(REPLACE ";" " " _schema_extra_args "${_schema_extra_args}") add_test(NAME schema-check COMMAND ${BASH_PROGRAM} "${PROJECT_SOURCE_DIR}/utils/codegen/generate-schema.sh" --check) set_tests_properties(schema-check PROPERTIES ENVIRONMENT - "MRDOCS=$;ADDONS=${PROJECT_SOURCE_DIR}/data/mrdocs/addons;MRDOCS_INPUT=${PROJECT_SOURCE_DIR}/CMakeLists.txt;MRDOCS_EXTRA_ARGS=${MRDOCS_SCHEMA_STDLIB} --libc-includes=${PROJECT_SOURCE_DIR}/data/mrdocs/headers/libc-stubs --concurrency=16") + "MRDOCS=$;ADDONS=${PROJECT_SOURCE_DIR}/data/mrdocs/addons;MRDOCS_INPUT=${PROJECT_SOURCE_DIR}/CMakeLists.txt;MRDOCS_EXTRA_ARGS=${_schema_extra_args} --concurrency=16") endif() diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index d4c2f9f11ad..5e8c228b72c 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -8,12 +8,6 @@ # Official repository: https://github.com/cppalliance/mrdocs # -# The tools link mrdocs-core, which the header-only "MrDocs build" does not -# build, so skip tools/ in that mode. -if (MRDOCS_BUILD_HEADERS_ONLY) - return() -endif() - # Command-line tools. Each tool lives in its own subdirectory with its own # CMakeLists; the mrdocs CLI is the only one so far. add_subdirectory(mrdocs) diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 405e6a8ccf6..00978053e01 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -21,3 +21,4 @@ add_subdirectory(danger) add_subdirectory(docs) add_subdirectory(linting) add_subdirectory(testing) +add_subdirectory(cmake) diff --git a/utils/cmake/CMakeLists.txt b/utils/cmake/CMakeLists.txt new file mode 100644 index 00000000000..7f2d7e60575 --- /dev/null +++ b/utils/cmake/CMakeLists.txt @@ -0,0 +1,13 @@ +# +# 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) 2026 Alan de Freitas (alandefreitas@gmail.com) +# +# Official repository: https://github.com/cppalliance/mrdocs +# + +include(helpers.cmake) +include(install.cmake) + diff --git a/utils/cmake/helpers.cmake b/utils/cmake/helpers.cmake index 591da643abc..3f9d052ea92 100644 --- a/utils/cmake/helpers.cmake +++ b/utils/cmake/helpers.cmake @@ -8,6 +8,10 @@ # Official repository: https://github.com/cppalliance/mrdocs # +# Included from the root early, before add_subdirectory(utils), because +# set_ternary is used there. +include_guard(GLOBAL) + # set_ternary( ) # # CMake has no configure-time ternary, so this stands in for the recurring @@ -29,4 +33,94 @@ macro(set_ternary out_var cond val_true val_false) set(${out_var} \"${val_false}\") endif () ") -endmacro() \ No newline at end of file +endmacro() + +# mrdocs_collect_static_libs( ...) +# +# Breadth-first walk of the given targets' link graph, splitting what it finds: +# : the transitive static-library files (as $), for +# merging into a bundle; +# : the transitive OS/system libraries (pthread, ntdll, ...) named +# as non-targets, which cannot be bundled and must be relinked. +function(mrdocs_collect_static_libs out_var out_system_var) + # Breadth-first walk of the link graph. + set(_static_libs "") # the static-library files found, as $ + set(_system_libs "") # non-target link items (system libraries / flags) + set(_visited "") # targets already processed (guards against cycles) + set(_to_visit "${ARGN}") # worklist of targets still to process + + while(_to_visit) + list(POP_FRONT _to_visit _entry) + + # Peel that generator expressions (e.g.: $ -> mrdocs-llvm) + string(REGEX REPLACE "^\\$<[A-Za-z_]+:(.+)>$" "\\1" _entry "${_entry}") + + # A non-target entry is a system library (pthread, ntdll, ...) or a raw + # linker flag: it can't be bundled so we return it so it can be remembered + # and relinked at install time later. + # When the toolchain is bundled the imported targets that would otherwise + # carry these are dropped, so a consumer needs them on its own link line. + if (NOT _entry OR NOT TARGET ${_entry}) + # Genex fragments left by an incomplete peel are not libraries: skip those. + if(_entry AND NOT _entry MATCHES "[$<>]") + list(APPEND _system_libs "${_entry}") + endif() + continue() + endif() + + # Resolve an alias to the target it stands for: (e.g. mrdocs::dom -> mrdocs-dom) + get_target_property(_aliased ${_entry} ALIASED_TARGET) + if(_aliased) + set(_entry ${_aliased}) + endif() + + # Process each target once in graph diamonds + if(_entry IN_LIST _visited) + continue() + endif() + list(APPEND _visited ${_entry}) + + # Store this node in the result when it is a static archive we can merge. + get_target_property(_type ${_entry} TYPE) + if(_type STREQUAL "STATIC_LIBRARY") + list(APPEND _static_libs "$") + elseif(_type STREQUAL "UNKNOWN_LIBRARY") + # A "Find" module located the file by path (find_library) + # Merge it ONLY if that file is actually a static + set(_loc "") + get_target_property(_configs ${_entry} IMPORTED_CONFIGURATIONS) + if(_configs) + list(GET _configs 0 _cfg) + get_target_property(_loc ${_entry} IMPORTED_LOCATION_${_cfg}) + endif() + if(NOT _loc) + get_target_property(_loc ${_entry} IMPORTED_LOCATION) + endif() + string(REPLACE "." "\\." _static_suffix "${CMAKE_STATIC_LIBRARY_SUFFIX}") + if(_loc AND _loc MATCHES "${_static_suffix}$") + list(APPEND _static_libs "$") + endif() + endif() + + # Queue this target's public and private linked libraries + get_target_property(_interface_libs ${_entry} INTERFACE_LINK_LIBRARIES) + if(_interface_libs) + list(APPEND _to_visit ${_interface_libs}) + endif() + if(NOT _type STREQUAL "INTERFACE_LIBRARY") + get_target_property(_private_libs ${_entry} LINK_LIBRARIES) + if(_private_libs) + list(APPEND _to_visit ${_private_libs}) + endif() + endif() + endwhile() + + if(_static_libs) + list(REMOVE_DUPLICATES _static_libs) + endif() + if(_system_libs) + list(REMOVE_DUPLICATES _system_libs) + endif() + set(${out_var} "${_static_libs}" PARENT_SCOPE) + set(${out_system_var} "${_system_libs}" PARENT_SCOPE) +endfunction() diff --git a/utils/cmake/install.cmake b/utils/cmake/install.cmake index 0c5e2220d5a..5cce19a8161 100644 --- a/utils/cmake/install.cmake +++ b/utils/cmake/install.cmake @@ -8,26 +8,25 @@ # Official repository: https://github.com/cppalliance/mrdocs # -# Build-support CMake module. It does nothing on include: it only defines -# functions the project can call. This is where project build/packaging helpers -# live (utils/ is developer/build tooling; utils/cmake/ groups its CMake -# modules, as data/cmake/ groups the shipped consumer-facing ones). Include it -# from the root to get the functions, then call them where appropriate. +# Build-support CMake module. +# +# It does nothing on include: it only defines the mrdocs_install function +# the project can call. +# +include_guard(GLOBAL) # mrdocs_install() # -# Finalizes installation and packaging. The individual targets install -# themselves next to their definitions (mrdocs-core in src/, mrdocs in tools/, -# shared data in data/); this finalizes the single mrdocs-targets export (which -# spans src/ and tools/, so it must be done once, after both are registered), -# writes the package config files, and configures CPack. Call it from the root -# after every target has been added. It self-guards, so calling it is always -# safe. +# Finalizes installation and packaging. +# +# The individual targets install themselves next to their definitions +# (mrdocs-core in src/, mrdocs in tools/, shared data in data/). +# +# This finalizes the single mrdocs-targets export (which spans src/ and tools/, +# so it must be done once, after both are registered). +# +# It writes the package config files, and configures CPack if packages are created. function(mrdocs_install) - if (MRDOCS_BUILD_HEADERS_ONLY OR NOT MRDOCS_INSTALL) - return() - endif() - install(EXPORT mrdocs-targets FILE mrdocs-targets.cmake NAMESPACE mrdocs:: @@ -46,13 +45,15 @@ function(mrdocs_install) DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mrdocs) # mrdocs-config.cmake - set(INCLUDE_INSTALL_DIR include/) - set(LIB_INSTALL_DIR lib/) + set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR}) + set(LIB_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}) + set(BIN_INSTALL_DIR ${CMAKE_INSTALL_BINDIR}) + set(DATAROOT_INSTALL_DIR ${CMAKE_INSTALL_DATAROOTDIR}) configure_package_config_file( ${CMAKE_CURRENT_SOURCE_DIR}/mrdocs-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/mrdocs-config.cmake INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mrdocs - PATH_VARS CMAKE_INSTALL_LIBDIR INCLUDE_INSTALL_DIR LIB_INSTALL_DIR) + PATH_VARS CMAKE_INSTALL_LIBDIR INCLUDE_INSTALL_DIR LIB_INSTALL_DIR BIN_INSTALL_DIR DATAROOT_INSTALL_DIR) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/mrdocs-config.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mrdocs) @@ -66,20 +67,24 @@ function(mrdocs_install) # CPack packaging. if (MRDOCS_PACKAGE) - set(CPACK_PACKAGE_VENDOR "mrdocs") - set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${PROJECT_DESCRIPTION}) - set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) - set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) - set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) - set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt") - set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.adoc") + mrdocs_package() + endif() +endfunction() - # Ignore files (from .gitignore) - FILE(READ ${CMAKE_CURRENT_SOURCE_DIR}/.gitignore GITIGNORE_CONTENTS) - STRING(REGEX REPLACE ";" "\\\\;" GITIGNORE_CONTENTS "${GITIGNORE_CONTENTS}") - STRING(REGEX REPLACE "\n" ";" GITIGNORE_CONTENTS "${GITIGNORE_CONTENTS}") - set(CPACK_SOURCE_IGNORE_FILES ${GITIGNORE_CONTENTS}) +function(mrdocs_package) + set(CPACK_PACKAGE_VENDOR "mrdocs") + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${PROJECT_DESCRIPTION}) + set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) + set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) + set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) + set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt") + set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.adoc") - include(CPack) - endif() + # Ignore files (from .gitignore) + FILE(READ ${CMAKE_CURRENT_SOURCE_DIR}/.gitignore GITIGNORE_CONTENTS) + STRING(REGEX REPLACE ";" "\\\\;" GITIGNORE_CONTENTS "${GITIGNORE_CONTENTS}") + STRING(REGEX REPLACE "\n" ";" GITIGNORE_CONTENTS "${GITIGNORE_CONTENTS}") + set(CPACK_SOURCE_IGNORE_FILES ${GITIGNORE_CONTENTS}) + + include(CPack) endfunction() diff --git a/utils/codegen/CMakeLists.txt b/utils/codegen/CMakeLists.txt index e0d5090e4f1..eb382d23c8d 100644 --- a/utils/codegen/CMakeLists.txt +++ b/utils/codegen/CMakeLists.txt @@ -14,75 +14,28 @@ # (mrdocs-core, the mrdocs tool, the golden tests) and before the src/ header # build that parses them. -# ---- include/mrdocs/Version.hpp from Version.hpp.in ---- -if (MRDOCS_REQUIRE_GIT) - find_package(Git REQUIRED) -else() - find_package(Git QUIET) -endif() -set(PROJECT_VERSION_BUILD "") -set(PROJECT_VERSION_WITH_BUILD "${PROJECT_VERSION}") # default: plain semver -if (EXISTS "${PROJECT_SOURCE_DIR}/.git" AND GIT_FOUND) - # Get full SHA - execute_process( - COMMAND ${GIT_EXECUTABLE} rev-parse HEAD - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - OUTPUT_VARIABLE GIT_SHA_FULL - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE GIT_SHA_FULL_RV - ERROR_QUIET - ) - if (NOT GIT_SHA_FULL_RV EQUAL 0) - message(FATAL_ERROR "Git was found but could not extract commit SHA") - endif() - set(PROJECT_VERSION_BUILD "${GIT_SHA_FULL}") - string(SUBSTRING "${GIT_SHA_FULL}" 0 12 GIT_SHA_SHORT) - - # Are we exactly at a tag? - execute_process( - COMMAND ${GIT_EXECUTABLE} describe --tags --exact-match HEAD - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - OUTPUT_VARIABLE GIT_EXACT_TAG - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE GIT_EXACT_TAG_RV - ERROR_QUIET - ) - if (GIT_EXACT_TAG_RV EQUAL 0) - # On a tag: canonical release - set(PROJECT_VERSION_WITH_BUILD "${PROJECT_VERSION}") - else() - # Dirty working tree? (0 = clean, 1 = dirty) - execute_process( - COMMAND ${GIT_EXECUTABLE} diff --quiet --ignore-submodules - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - RESULT_VARIABLE GIT_DIRTY_RV - ERROR_QUIET - ) - set(_dirty_suffix "") - if (GIT_DIRTY_RV EQUAL 1) - set(_dirty_suffix ".modified") - endif() - set(PROJECT_VERSION_WITH_BUILD - "${PROJECT_VERSION}+${GIT_SHA_SHORT}${_dirty_suffix}") - endif() -else() - if (MRDOCS_REQUIRE_GIT) - message(FATAL_ERROR "Git is required to extract the version build") - endif() - set(PROJECT_VERSION_BUILD "") - set(PROJECT_VERSION_WITH_BUILD "${PROJECT_VERSION}") -endif() -configure_file( - ${PROJECT_SOURCE_DIR}/include/mrdocs/Version.hpp.in - ${PROJECT_BINARY_DIR}/include/mrdocs/Version.hpp - @ONLY -) - -# ---- include/mrdocs/ConfigSchema.hpp + related files from the JSON config ---- find_program(PYTHON_EXECUTABLE python3 python) if (NOT PYTHON_EXECUTABLE) message(FATAL_ERROR "Python is needed to configure mrdocs") endif() + +# ---- include/mrdocs/Version.hpp from Version.hpp.in ---- +set_ternary(MRDOCS_REQUIRE_GIT_OPT MRDOCS_REQUIRE_GIT REQUIRED QUIET) +find_package(Git ${MRDOCS_REQUIRE_GIT_OPT}) +add_custom_target(mrdocs-version ALL + BYPRODUCTS ${PROJECT_BINARY_DIR}/include/mrdocs/Version.hpp + COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/generate-version-header.py + ${PROJECT_SOURCE_DIR}/include/mrdocs/Version.hpp.in + ${PROJECT_BINARY_DIR}/include/mrdocs/Version.hpp + --version ${PROJECT_VERSION} + --name ${PROJECT_NAME} + --description "${PROJECT_DESCRIPTION}" + --source-dir ${PROJECT_SOURCE_DIR} + --git "${GIT_EXECUTABLE}" + COMMENT "Generating Version.hpp" + VERBATIM) + +# ---- include/mrdocs/ConfigSchema.hpp from ConfigOptions.json ---- set(CONFIG_GEN_WORKING_DIR "${PROJECT_SOURCE_DIR}") set(CONFIG_GEN_OUTPUT_DIR "${PROJECT_BINARY_DIR}") set(CONFIG_GEN_CONFIG_JSON "${PROJECT_SOURCE_DIR}/src/mrdocs/ConfigOptions.json") @@ -98,36 +51,23 @@ set(CONFIG_GEN_INPUT_FILES set(CONFIG_GEN_OUTPUT_FILES ${CONFIG_GEN_OUTPUT_DIR}/include/mrdocs/ConfigSchema.hpp ) -if (MRDOCS_BUILD_HEADERS_ONLY) - # Create the files at configure time: the src/ header build parses the - # generated public headers immediately and defines no build-time targets. - execute_process( - COMMAND ${CONFIG_GEN_COMMAND_INFO} - WORKING_DIRECTORY ${CONFIG_GEN_WORKING_DIR} - COMMAND_ERROR_IS_FATAL ANY - ) - execute_process( - COMMAND ${CONFIG_GEN_COMMAND_SCHEMA} - WORKING_DIRECTORY ${CONFIG_GEN_WORKING_DIR} - COMMAND_ERROR_IS_FATAL ANY - ) - set_source_files_properties(${CONFIG_GEN_OUTPUT_FILES} PROPERTIES GENERATED TRUE) -else() - # Generate at build time. The outputs are listed as sources by mrdocs-core, - # the mrdocs tool, and the golden tests; mrdocs-codegen gives those targets - # a stable dependency to order against across directories. - add_custom_command( - OUTPUT ${CONFIG_GEN_OUTPUT_FILES} - COMMAND ${CONFIG_GEN_COMMAND_INFO} - COMMAND ${CONFIG_GEN_COMMAND_SCHEMA} - WORKING_DIRECTORY ${CONFIG_GEN_WORKING_DIR} - DEPENDS ${CONFIG_GEN_INPUT_FILES} - COMMENT "Generating Config Source Files" - VERBATIM - COMMAND_EXPAND_LISTS - ) - add_custom_target(mrdocs-codegen DEPENDS ${CONFIG_GEN_OUTPUT_FILES}) -endif() +# Generate at build time. The outputs are listed as sources by mrdocs-core, the +# mrdocs tool, and the golden tests; mrdocs-codegen gives those targets a stable +# dependency to order against across directories. +add_custom_command( + OUTPUT ${CONFIG_GEN_OUTPUT_FILES} + COMMAND ${CONFIG_GEN_COMMAND_INFO} + COMMAND ${CONFIG_GEN_COMMAND_SCHEMA} + WORKING_DIRECTORY ${CONFIG_GEN_WORKING_DIR} + DEPENDS ${CONFIG_GEN_INPUT_FILES} + COMMENT "Generating Config Source Files" + VERBATIM + COMMAND_EXPAND_LISTS +) +add_custom_target(mrdocs-codegen DEPENDS ${CONFIG_GEN_OUTPUT_FILES}) +# mrdocs-core, the mrdocs tool, and the golden tests all depend on mrdocs-codegen; +# chaining the version target here orders it before them without extra wiring. +add_dependencies(mrdocs-codegen mrdocs-version) # ---- schema check (mirrors the CI "Check YAML schema" step) ---- # The published config schema (docs/modules/ROOT/attachments/schemas/ diff --git a/utils/codegen/generate-config-info.py b/utils/codegen/generate-config-info.py index 4e28797c7dd..a25167affb4 100644 --- a/utils/codegen/generate-config-info.py +++ b/utils/codegen/generate-config-info.py @@ -819,7 +819,12 @@ def generate_option_declaration(option, style=None): # Enums should be initialized in the hpp file cpp_default_value = to_cpp_default_value(option, True) if cpp_default_value: - contents += f' = {cpp_default_value}' + # Cast a negative default for an unsigned field so MSVC does not warn + # C4245 (signed/unsigned mismatch), e.g. `unsigned report = -1`. + if option["type"] == 'unsigned' and str(cpp_default_value).startswith('-'): + contents += f' = static_cast({cpp_default_value})' + else: + contents += f' = {cpp_default_value}' contents += ';' return contents diff --git a/utils/codegen/generate-version-header.py b/utils/codegen/generate-version-header.py new file mode 100644 index 00000000000..5b6c46a5c26 --- /dev/null +++ b/utils/codegen/generate-version-header.py @@ -0,0 +1,101 @@ +# +# 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) 2026 Alan de Freitas (alandefreitas@gmail.com) +# +# Official repository: https://github.com/cppalliance/mrdocs +# + +# Generates Version.hpp from Version.hpp.in by substituting @PROJECT_VERSION@ and +# friends, stamping the git build metadata. Like the other codegen scripts, it is +# meant to run at build time so the SHA reflects the commit being built; it only +# rewrites the output when the content changes, so it never forces a rebuild. + +import argparse +import os +import subprocess + + +def git(git_exe, args, source_dir): + return subprocess.run( + [git_exe, *args], cwd=source_dir, + capture_output=True, text=True) + + +def version_with_build(git_exe, version, source_dir): + """Return (version_with_build, full_sha) from the git state. + + Git is optional here: CMake already found it (or decided it was not required) + before this runs, so on any git failure we just fall back to the plain version. + """ + try: + head = git(git_exe, ["rev-parse", "HEAD"], source_dir) + except OSError: + # git executable not found: fall back to the plain version. + return version, "" + if head.returncode != 0: + return version, "" + + full_sha = head.stdout.strip() + short_sha = full_sha[:12] + + # Exactly on a tag: canonical release, no build metadata. + on_tag = git(git_exe, ["describe", "--tags", "--exact-match", "HEAD"], source_dir).returncode == 0 + if on_tag: + return version, full_sha + + # Off a tag: append the short SHA, plus .modified if the tree is dirty. + dirty = git(git_exe, ["diff", "--quiet", "--ignore-submodules"], source_dir).returncode == 1 + suffix = ".modified" if dirty else "" + return f"{version}+{short_sha}{suffix}", full_sha + + +def main(): + parser = argparse.ArgumentParser(description="Generate Version.hpp from Version.hpp.in") + parser.add_argument("template", help="path to Version.hpp.in") + parser.add_argument("output", help="path to the Version.hpp to generate") + parser.add_argument("--version", required=True, help="project version (MAJOR.MINOR.PATCH)") + parser.add_argument("--name", default="", help="project name") + parser.add_argument("--description", default="", help="project description") + parser.add_argument("--source-dir", default=".", help="repository root, used for the git queries") + parser.add_argument("--git", default="git", + help="git executable (CMake passes the one it found; defaults to PATH)") + args = parser.parse_args() + + git_exe = args.git or "git" + build_version, build_sha = version_with_build(git_exe, args.version, args.source_dir) + # Split MAJOR.MINOR.PATCH, defaulting any missing component to 0. + parts = (args.version.split(".") + ["0", "0", "0"])[:3] + substitutions = { + "PROJECT_NAME": args.name, + "PROJECT_DESCRIPTION": args.description, + "PROJECT_VERSION": args.version, + "PROJECT_VERSION_MAJOR": parts[0], + "PROJECT_VERSION_MINOR": parts[1], + "PROJECT_VERSION_PATCH": parts[2], + "PROJECT_VERSION_WITH_BUILD": build_version, + "PROJECT_VERSION_BUILD": build_sha, + } + + content = '' + with open(args.template, "r") as f: + content = f.read() + for name, value in substitutions.items(): + content = content.replace(f"@{name}@", value) + + # Only write when the content changed, so an unchanged SHA never touches the + # file (and never triggers a rebuild of everything that includes it). + previous = None + if os.path.exists(args.output): + with open(args.output, "r") as f: + previous = f.read() + if content != previous: + os.makedirs(os.path.dirname(args.output), exist_ok=True) + with open(args.output, "w") as f: + f.write(content) + + +if __name__ == "__main__": + main()