Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ jobs:
- name: Run target type tests
run: cmake -P tests/setup/test_target_type.cmake

- name: Run custom executable runtime deployment tests
run: cmake -P tests/setup/test_runtime_dlls.cmake

- name: Run setup.cmake version resolution test
run: cmake -P tests/setup/test_setup_version_resolution.cmake

Expand All @@ -44,6 +47,9 @@ jobs:
- name: Run target type tests
run: cmake -P tests/setup/test_target_type.cmake

- name: Run custom executable runtime deployment tests
run: cmake -P tests/setup/test_runtime_dlls.cmake

- name: Run nested static and shared installation round trips
run: cmake -P tests/install/test_nested_install.cmake

Expand Down Expand Up @@ -165,6 +171,13 @@ jobs:
# [DEPENDENCY] https://github.com/actions/checkout/releases
- uses: actions/checkout@v7

- name: Install Doxygen
# [DEPENDENCY] https://github.com/ssciwr/doxygen-install/releases
uses: ssciwr/doxygen-install@fa3e94d34131d3990b6b8ba939ab982964aa3758

- name: Test documentation configuration and rendered output
run: cmake -P tests/docs/test_docs.cmake

- name: Check README examples
run: |
# Extract and validate code blocks from README
Expand Down
11 changes: 6 additions & 5 deletions .scout/scout.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
# ignore / dampen / boost / tune overrides; defaults for
# repository/markdown/ranking/index/projects are filled in at load time.
ignore:
- '**/CHANGELOG.md'
- '**/CHANGELOG*'
- '**/CHANGES.md'
- '**/HISTORY.md'
- "**/CHANGELOG.md"
- "**/CHANGELOG*"
- "**/CHANGES.md"
- "**/HISTORY.md"
- .claude/worktrees/

dampen:
- factor: 0.3
patterns:
- '**/package.json'
- "**/package.json"
11 changes: 8 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,20 @@ Two ways consumers use this repo:

## Commands

Run cpp-library's own test suite (pure CMake scripts, no C++ compilation involved):
Run cpp-library's tests through CMake scripts. Integration fixtures require a
C++ compiler and Ninja; documentation rendering also requires Doxygen:

```bash
cmake -P tests/install/CMakeLists.txt # dependency mapping/merging unit tests
cmake -P tests/install/test_provider_merge.cmake # dependency provider merge tests
cmake -P tests/setup/test_setup_version_resolution.cmake # setup.cmake version-resolution integration test
cmake -P tests/setup/test_runtime_dlls.cmake # custom target DLL deployment; requires compiler and Ninja
cmake -P tests/docs/test_docs.cmake # docs extensions and rendering; requires Doxygen and Ninja
```

These are the same commands CI (`.github/workflows/ci.yml`, job `unit-tests`) runs. There is no build step for cpp-library itself — `cmake -P` executes the scripts directly.
CI (`.github/workflows/ci.yml`) runs these scripts in its unit-test, Windows
package, and documentation jobs. There is no library build step for cpp-library
itself; integration scripts configure and build their own consumer fixtures.

CI also runs an `integration-tests` job that generates a throwaway consumer project, configures/builds/installs it against this repo via `CPMAddPackage(... SOURCE_DIR ...)`, then verifies `find_package()` works against the installed package — this is the best reference for the full round-trip a downstream project goes through.

Expand All @@ -45,7 +50,7 @@ or pin to a specific commit: `CPMAddPackage("gh:stlab/cpp-library#<commit-sha>")
- **`cpp-library-dependency-provider.cmake`** — implements the CMake dependency-provider callback (`SET_DEPENDENCY_PROVIDER`) that intercepts every `find_package()`/`FetchContent_MakeAvailable()` call to record the exact package/version/components used, so `Config.cmake` can regenerate accurate `find_dependency()` calls at install time. State is stored in `GLOBAL` properties (`_CPP_LIBRARY_TRACKED_DEP_*`, `_CPP_LIBRARY_PKG_KEYS`, etc.) — there's no other persistence mechanism, so tests reset these properties between cases (see `tests/install/CMakeLists.txt`).
- **`cpp-library-docs.cmake`** — Doxygen + doxygen-awesome-css target (`docs`).
- **`cpp-library-ci.cmake`** — generates `.github/workflows/ci.yml` for the *consumer* project from `templates/.github/workflows/ci.yml.in`, substituting the package name.
- **`cpp-library-testing.cmake`** — thin backward-compat wrapper; real test/example executable logic lives in `_cpp_library_setup_executables()` in `cpp-library.cmake`.
- **`cpp-library-testing.cmake`** — public `cpp_library_copy_runtime_dlls()` helper and backward-compatibility `_cpp_library_setup_testing()` wrapper; test/example executable creation lives in `_cpp_library_setup_executables()` in `cpp-library.cmake`.

### Key control flow

Expand Down
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@ cpp_library_setup(
[EXAMPLES example_list] # Example source files to build (e.g., "example.cpp example_fail.cpp")
[TESTS test_list] # Test source files to build (e.g., "tests.cpp")
[DOCS_EXCLUDE_SYMBOLS symbols] # Symbols to exclude from docs
[DOCS_INPUTS input_list] # Additional documentation files/directories
[DOCS_OPTIONS setting_list] # Additive single-line Doxygen assignments
[REQUIRES_CPP_VERSION 17|20|23] # C++ version (default: 17)
[INSTALL_OPTION option_name] # Independent installation control (default: uppercase NAMESPACE_INSTALL)
)
Expand All @@ -374,6 +376,57 @@ cpp_library_setup(
- Installation is controlled by `INSTALL_OPTION`, or the uppercase `${NAMESPACE}_INSTALL` option
when omitted. Options default to `PROJECT_IS_TOP_LEVEL`.

### Documentation configuration

Keep the toolkit's Doxyfile template instead of copying it into your project.
`DOCS_INPUTS` adds files or directories to the default `include` input. Relative
paths are resolved against the directory calling `cpp_library_setup`; absolute
paths and paths containing spaces are supported. Missing inputs fail configuration.

`DOCS_OPTIONS` applies project-specific Doxygen assignments after the shared
template. Each quoted CMake argument must be a single `TAG = value` or
`TAG += value` assignment, with Doxygen quoting inside the value when needed.
Use spaces, not CMake semicolons, to separate Doxygen list values. The toolkit
validates assignment syntax and rejects line-continuation backslashes, including
those followed by whitespace; Doxygen validates tag names and values. Settings
are literal, not `configure_file` templates: use `${...}` for CMake variables.

```cmake
cpp_library_setup(
DESCRIPTION "My library"
NAMESPACE mylib
HEADERS api.hpp
DOCS_INPUTS docs/doxygen "${PROJECT_BINARY_DIR}/generated docs"
DOCS_EXCLUDE_SYMBOLS "mylib::detail"
DOCS_OPTIONS
"WARN_IF_UNDOCUMENTED = NO"
"INCLUDE_PATH = \"${PROJECT_BINARY_DIR}/include\""
"MACRO_EXPANSION = YES"
"PREDEFINED = MYLIB_API= MYLIB_NAMESPACE_BEGIN()= MYLIB_NAMESPACE_END()="
)
```

Existing `docs/Doxyfile` overrides remain supported. Additional inputs and options
are applied after that configuration too, but a full override does not inherit
future toolkit-template changes. Prefer the shared template with only the
project-specific deltas above. Documentation is configured only for top-level
projects with `BUILD_DOCS` enabled.

### `cpp_library_copy_runtime_dlls`

```cmake
add_executable(custom-test main.cpp support.cpp)
target_link_libraries(custom-test PRIVATE mylib::my-library)
cpp_library_copy_runtime_dlls(custom-test)
```

Call this helper in the directory that created the target. On Windows, it deploys
`TARGET_RUNTIME_DLLS` beside the target after building, including transitive
runtime DLLs. Static targets with no runtime DLLs require no copy; on other
platforms the helper does nothing. It uses CMake 3.24-compatible copy operations
and handles DLLs already in the destination. Toolkit-created test/example
executables use the same helper automatically.

### Target Naming

Use the component name as your project name, and specify the organizational namespace separately:
Expand Down
24 changes: 24 additions & 0 deletions cmake/cpp-library-docs.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,30 @@ function(_cpp_library_setup_docs)
)
set(multiValueArgs
DOCS_EXCLUDE_SYMBOLS
DOCS_INPUTS
DOCS_OPTIONS
)

cmake_parse_arguments(ARG "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

set(docs_extensions "")
foreach(input IN LISTS ARG_DOCS_INPUTS)
get_filename_component(input "${input}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
if(NOT EXISTS "${input}")
message(FATAL_ERROR "cpp-library: Documentation input does not exist: ${input}")
endif()
string(REPLACE "\"" "\\\"" input "${input}")
string(APPEND docs_extensions "INPUT += \"${input}\"\n")
endforeach()
foreach(option IN LISTS ARG_DOCS_OPTIONS)
if(NOT option MATCHES "^[A-Z][A-Z0-9_]*[ \t]*\\+?=[^\r\n]*$"
OR option MATCHES "\\\\[ \t]*$")
message(FATAL_ERROR
"cpp-library: DOCS_OPTIONS requires single-line Doxygen assignments: ${option}")
endif()
string(APPEND docs_extensions "${option}\n")
endforeach()

find_package(Doxygen REQUIRED)

# Download doxygen-awesome-css theme via CPM
Expand Down Expand Up @@ -60,6 +80,10 @@ function(_cpp_library_setup_docs)
else()
configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY)
endif()
if(NOT docs_extensions STREQUAL "")
file(APPEND "${DOXYFILE_OUT}"
"\n# Project documentation settings supplied through cpp_library_setup.\n${docs_extensions}")
endif()

# Add custom target for documentation with proper stderr capture
if(WIN32)
Expand Down
23 changes: 19 additions & 4 deletions cmake/cpp-library-testing.cmake
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
# SPDX-License-Identifier: BSL-1.0
#
# cpp-library-testing.cmake - Testing setup with doctest
# cpp-library-testing.cmake - Runtime DLL deployment and testing compatibility
#
# Note: Testing logic has been consolidated into the main cpp-library.cmake file
# This file is kept for backward compatibility but the actual implementation
# is now in the _cpp_library_setup_executables function.
# Provides cpp_library_copy_runtime_dlls for custom and toolkit-created executables.
# _cpp_library_setup_testing delegates to _cpp_library_setup_executables for compatibility.

# Deploys runtime DLLs beside a custom target on Windows; empty lists need no copy.
Comment thread
sean-parent marked this conversation as resolved.
# Call in the directory that created the target, as required by POST_BUILD commands.
function(cpp_library_copy_runtime_dlls target)
if(NOT TARGET "${target}")
message(FATAL_ERROR "cpp_library_copy_runtime_dlls: Target does not exist: ${target}")
endif()
if(WIN32)
add_custom_command(TARGET ${target} POST_BUILD
COMMAND "${CMAKE_COMMAND}"
"-DCPP_LIBRARY_RUNTIME_DLLS=$<TARGET_RUNTIME_DLLS:${target}>"
"-DCPP_LIBRARY_RUNTIME_DESTINATION=$<TARGET_FILE_DIR:${target}>"
-P "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/cpp-library-copy-runtime-dlls.cmake"
VERBATIM)
endif()
endfunction()

# Delegates to _cpp_library_setup_executables for backward compatibility.
# - Postcondition: test executables configured via _cpp_library_setup_executables
Expand Down
19 changes: 10 additions & 9 deletions cpp-library.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -134,15 +134,7 @@ function(_cpp_library_setup_executables)
add_executable(${executable_base} "${source_dir}/${executable}")
target_link_libraries(${executable_base} PRIVATE ${ARG_NAMESPACE}::${CLEAN_NAME} doctest::doctest)

if(WIN32)
# A script handles empty DLL lists without requiring newer copy commands.
add_custom_command(TARGET ${executable_base} POST_BUILD
COMMAND "${CMAKE_COMMAND}"
"-DCPP_LIBRARY_RUNTIME_DLLS=$<TARGET_RUNTIME_DLLS:${executable_base}>"
"-DCPP_LIBRARY_RUNTIME_DESTINATION=$<TARGET_FILE_DIR:${executable_base}>"
-P "${CPP_LIBRARY_ROOT}/cmake/cpp-library-copy-runtime-dlls.cmake"
VERBATIM)
endif()
cpp_library_copy_runtime_dlls(${executable_base})

# Register as CTest test
add_test(NAME ${executable_base} COMMAND ${executable_base})
Expand Down Expand Up @@ -182,13 +174,20 @@ function(cpp_library_setup)
EXAMPLES # Example source files to build (e.g., "example.cpp example_fail.cpp")
TESTS # Test source files to build (e.g., "tests.cpp")
DOCS_EXCLUDE_SYMBOLS # Symbols to exclude from docs
DOCS_INPUTS # Additional documentation files or directories
DOCS_OPTIONS # Single-line Doxygen assignments applied after the template
)

cmake_parse_arguments(ARG "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

if("INSTALL_OPTION" IN_LIST ARG_KEYWORDS_MISSING_VALUES)
message(FATAL_ERROR "cpp_library_setup: INSTALL_OPTION requires a value")
endif()
foreach(keyword DOCS_INPUTS DOCS_OPTIONS)
if(keyword IN_LIST ARG_KEYWORDS_MISSING_VALUES)
message(FATAL_ERROR "cpp_library_setup: ${keyword} requires a value")
endif()
endforeach()
set(install_args)
if(DEFINED ARG_INSTALL_OPTION)
list(APPEND install_args INSTALL_OPTION "${ARG_INSTALL_OPTION}")
Expand Down Expand Up @@ -333,6 +332,8 @@ function(cpp_library_setup)
VERSION "${ARG_VERSION}"
DESCRIPTION "${ARG_DESCRIPTION}"
DOCS_EXCLUDE_SYMBOLS "${ARG_DOCS_EXCLUDE_SYMBOLS}"
DOCS_INPUTS "${ARG_DOCS_INPUTS}"
DOCS_OPTIONS "${ARG_DOCS_OPTIONS}"
)
endif()

Expand Down
12 changes: 6 additions & 6 deletions templates/Doxyfile.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@
PROJECT_NAME = "@PROJECT_NAME@"
PROJECT_BRIEF = "@PROJECT_BRIEF@"
PROJECT_NUMBER = @PROJECT_VERSION@
OUTPUT_DIRECTORY = @OUTPUT_DIR@
OUTPUT_DIRECTORY = "@OUTPUT_DIR@"
GENERATE_LATEX = NO
QUIET = YES
WARN_IF_UNDOCUMENTED = YES
INPUT = @INPUT_DIR@
INPUT = "@INPUT_DIR@"
RECURSIVE = YES
EXCLUDE_SYMBOLS = @EXCLUDE_SYMBOLS@
EXAMPLE_PATH = @EXAMPLES_PATH@
EXAMPLE_PATH = "@EXAMPLES_PATH@"
HIDE_UNDOC_NAMESPACES = NO
HTML_EXTRA_STYLESHEET = @AWESOME_CSS_PATH@/doxygen-awesome.css \
@AWESOME_CSS_PATH@/doxygen-awesome-sidebar-only.css \
@CPP_LIBRARY_ROOT@/templates/custom.css
HTML_EXTRA_STYLESHEET = "@AWESOME_CSS_PATH@/doxygen-awesome.css" \
"@AWESOME_CSS_PATH@/doxygen-awesome-sidebar-only.css" \
"@CPP_LIBRARY_ROOT@/templates/custom.css"
HTML_COLORSTYLE = LIGHT
GENERATE_TREEVIEW = YES
DISABLE_INDEX = NO
Expand Down
59 changes: 59 additions & 0 deletions tests/docs/fixtures/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
cmake_minimum_required(VERSION 3.24)
project(docs-fixture LANGUAGES CXX)
include("${TOOLKIT_SOURCE}/cpp-library.cmake")
set(CPP_LIBRARY_VERSION 1.2.3)
set(FIXTURE_INSTALL OFF CACHE BOOL "")
set(BUILD_DOCS ON)

# Replace only the network theme download; exercise real configuration and Doxygen.
function(CPMAddPackage)
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/theme")
foreach(stylesheet doxygen-awesome.css doxygen-awesome-sidebar-only.css)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/theme/${stylesheet}" "")
endforeach()
set(doxygen-awesome-css_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/theme" PARENT_SCOPE)
endfunction()

set(docs_args)
if(CASE STREQUAL "extended")
list(APPEND docs_args
DOCS_INPUTS "extra docs" "${CMAKE_CURRENT_SOURCE_DIR}/absolute docs/group.dox"
DOCS_OPTIONS
"PROJECT_BRIEF = \"Customized documentation\""
"WARN_IF_UNDOCUMENTED = NO"
[=[PREDEFINED = "FIXTURE_PATH=C:\\docs\\"]=]
"EXCLUDE_SYMBOLS += fixture::extra"
DOCS_EXCLUDE_SYMBOLS "fixture::hidden")
elseif(CASE STREQUAL "missing-input")
list(APPEND docs_args DOCS_INPUTS "missing docs")
elseif(CASE STREQUAL "malformed-option")
list(APPEND docs_args DOCS_OPTIONS "not a Doxygen assignment")
elseif(CASE STREQUAL "malformed-terminal-continuation-option")
list(APPEND docs_args DOCS_OPTIONS [=[PREDEFINED = A= \]=])
elseif(CASE STREQUAL "malformed-continuation-option")
list(APPEND docs_args DOCS_OPTIONS
[=[PREDEFINED = A= \ ]=]
"WARN_IF_UNDOCUMENTED = NO")
elseif(CASE STREQUAL "multiline-option")
list(APPEND docs_args DOCS_OPTIONS "QUIET = YES\nGENERATE_XML = YES")
elseif(CASE STREQUAL "missing-input-value")
list(APPEND docs_args DOCS_INPUTS)
elseif(CASE STREQUAL "missing-option-value")
list(APPEND docs_args DOCS_OPTIONS)
elseif(CASE STREQUAL "custom")
file(MAKE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/docs")
file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile"
"PROJECT_NAME = \"Legacy configuration\"\n"
"INPUT = \"@INPUT_DIR@\"\n"
"OUTPUT_DIRECTORY = \"@OUTPUT_DIR@\"\n"
"GENERATE_LATEX = NO\nQUIET = YES\nWARN_AS_ERROR = YES\n")
list(APPEND docs_args DOCS_INPUTS "extra docs"
DOCS_OPTIONS "PROJECT_BRIEF = \"Customized documentation\"")
endif()

cpp_library_setup(
DESCRIPTION "Documentation fixture"
NAMESPACE fixture
INSTALL_OPTION FIXTURE_INSTALL
HEADERS api.hpp
${docs_args})
3 changes: 3 additions & 0 deletions tests/docs/fixtures/absolute docs/group.dox
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/** @defgroup extra_group Absolute fixture group
* @brief This group comes from an absolute input file.
*/
3 changes: 3 additions & 0 deletions tests/docs/fixtures/extra docs/mainpage.dox
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/** @mainpage Extra fixture content
* This page comes from an additional, space-containing input directory.
*/
12 changes: 12 additions & 0 deletions tests/docs/fixtures/include/fixture/api.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/** @file
* @brief Fixture public API.
*/
/** @brief Fixture namespace. */
namespace fixture {
/** @brief Return the fixture value. */
inline int value() { return 42; }
/** @brief Internal API omitted from the extended documentation. */
struct hidden {};
/** @brief Internal API omitted through an additive Doxygen option. */
struct extra {};
}
Loading
Loading