diff --git a/.gitattributes b/.gitattributes index 176a458..6313b56 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -* text=auto +* text=auto eol=lf diff --git a/.github/workflows/bvt-clang.yml b/.github/workflows/bvt-clang.yml index 1704856..c015a07 100644 --- a/.github/workflows/bvt-clang.yml +++ b/.github/workflows/bvt-clang.yml @@ -17,7 +17,7 @@ jobs: - name: Install clang run: | curl https://apt.llvm.org/llvm.sh | sudo bash -s -- "$CLANG_VERSION" - sudo apt install -y "clang-$CLANG_VERSION" "libc++-$CLANG_VERSION-dev" "clang-format-$CLANG_VERSION" + sudo apt install -y "clang-$CLANG_VERSION" "libc++-$CLANG_VERSION-dev" { echo "CC=clang-$CLANG_VERSION" echo "CXX=clang++-$CLANG_VERSION" @@ -27,7 +27,6 @@ jobs: - name: Check toolchain versions run: | "$CXX" --version - "clang-format-$CLANG_VERSION" --version cmake --version ninja --version meson --version @@ -36,9 +35,6 @@ jobs: - name: Build and run test with clang ${{ env.CLANG_VERSION }} on cmake run: | cmake -B build-cmake -GNinja -DCMAKE_CXX_STANDARD=23 -DCMAKE_BUILD_TYPE=Release -DPROXY_BUILD_MODULES=TRUE - mapfile -t FILES < <(find include tests benchmarks build-cmake/examples_from_docs tools -type f \( -name '*.h' -o -name '*.ixx' -o -name '*.cpp' \)) - echo "Running clang-format on ${#FILES[@]} files: ${FILES[*]}" - "clang-format-$CLANG_VERSION" --dry-run --Werror "${FILES[@]}" cmake --build build-cmake -j ctest --test-dir build-cmake -j mkdir build-cmake/drop diff --git a/.github/workflows/bvt-compatibility.yml b/.github/workflows/bvt-compatibility.yml index 8ee604d..859c547 100644 --- a/.github/workflows/bvt-compatibility.yml +++ b/.github/workflows/bvt-compatibility.yml @@ -34,10 +34,11 @@ jobs: - name: Install clang if: ${{ matrix.compiler.family == 'clang' }} run: | - if [ '${{ matrix.compiler.version }}' -ge 21 ]; then - curl https://apt.llvm.org/llvm.sh | sudo bash -s -- '${{ matrix.compiler.version }}' + version='${{ matrix.compiler.version }}' + if [ "$version" -ge 21 ]; then + curl https://apt.llvm.org/llvm.sh | sudo bash -s -- "$version" fi - sudo apt install -y 'clang-${{ matrix.compiler.version }}' 'clang-tools-${{ matrix.compiler.version }}' 'libc++-${{ matrix.compiler.version }}-dev' 'libc++abi-${{ matrix.compiler.version }}-dev' + sudo apt install -y "clang-$version" "clang-tools-$version" "libc++-$version-dev" "libc++abi-$version-dev" cat <<'EOF' >> "$GITHUB_ENV" CC=clang-${{ matrix.compiler.version }} CXX=clang++-${{ matrix.compiler.version }} diff --git a/.github/workflows/bvt-lint.yml b/.github/workflows/bvt-lint.yml new file mode 100644 index 0000000..5dc8bf6 --- /dev/null +++ b/.github/workflows/bvt-lint.yml @@ -0,0 +1,32 @@ +on: + workflow_call: + workflow_dispatch: + +jobs: + bvt-lint: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - name: Cache pre-commit envs + uses: actions/cache@v4 + with: + path: ~/.cache/pre-commit + key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }} + + - name: Install pre-commit + run: python3 -m pip install --upgrade pip pre-commit + + - name: Run pre-commit hooks + run: pre-commit run --all-files --show-diff-on-failure --color always + + - name: Install mkdocs + run: python3 -m pip install -r mkdocs/requirements.txt + + - name: Build docs (mkdocs --strict) + run: mkdocs build --strict diff --git a/.github/workflows/mkdocs.yml b/.github/workflows/mkdocs.yml deleted file mode 100644 index 603306f..0000000 --- a/.github/workflows/mkdocs.yml +++ /dev/null @@ -1,34 +0,0 @@ -on: - workflow_call: - inputs: - upload-artifacts: - type: boolean - default: false - -jobs: - bvt-mkdocs: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: "3.13" - architecture: "x64" - - - name: Install dependencies - run: | - python3 -m pip install --upgrade pip - python3 -m pip install -r mkdocs/requirements.txt - - - name: Build site - run: mkdocs build --strict - - - name: Upload site artifact - if: ${{ inputs.upload-artifacts }} - uses: actions/upload-pages-artifact@v5 - with: - path: ./site diff --git a/.github/workflows/pipeline-ci.yml b/.github/workflows/pipeline-ci.yml index f8037dc..9317cbe 100644 --- a/.github/workflows/pipeline-ci.yml +++ b/.github/workflows/pipeline-ci.yml @@ -8,6 +8,10 @@ on: workflow_dispatch: jobs: + run-bvt-lint: + uses: ./.github/workflows/bvt-lint.yml + name: Run BVT for lint + run-bvt-gcc: uses: ./.github/workflows/bvt-gcc.yml name: Run BVT with GCC @@ -40,7 +44,3 @@ jobs: uses: ./.github/workflows/bvt-report.yml name: Generate report needs: [run-bvt-gcc, run-bvt-clang, run-bvt-msvc, run-bvt-appleclang, run-bvt-nvhpc, run-bvt-oneapi] - - mkdocs: - uses: ./.github/workflows/mkdocs.yml - name: Build mkdocs diff --git a/.github/workflows/pipeline-pages.yml b/.github/workflows/pipeline-pages.yml index d44fb53..ab11ec7 100644 --- a/.github/workflows/pipeline-pages.yml +++ b/.github/workflows/pipeline-pages.yml @@ -7,9 +7,29 @@ on: jobs: build: - uses: ./.github/workflows/mkdocs.yml - with: - upload-artifacts: true + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + python3 -m pip install -r mkdocs/requirements.txt + + - name: Build site + run: mkdocs build --strict + + - name: Upload site artifact + uses: actions/upload-pages-artifact@v5 + with: + path: ./site deploy: needs: build diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..26fd9b8 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,56 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: mixed-line-ending + args: ["--fix=lf"] + - id: end-of-file-fixer + - id: trailing-whitespace + args: ["--markdown-linebreak-ext=md"] + - id: check-merge-conflict + - id: check-case-conflict + - id: check-illegal-windows-names + - id: check-added-large-files + args: ["--maxkb=1024"] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.15 + hooks: + - id: ruff-format + + - repo: https://github.com/BlankSpruce/gersemi-pre-commit + rev: 0.27.7 + hooks: + - id: gersemi + args: ["--indent", "2"] + exclude: '^(subprojects|build|bazel-.*)/' + + - repo: https://github.com/keith/pre-commit-buildifier + rev: 8.2.1 + hooks: + - id: buildifier + exclude: '^(subprojects|build|bazel-.*)/' + + - repo: https://github.com/rhysd/actionlint + rev: v1.7.8 + hooks: + - id: actionlint + + - repo: local + hooks: + - id: meson-format + name: meson format + language: python + additional_dependencies: ["meson==1.10.1"] + entry: meson + args: ["format", "--check-only", "--recursive", "meson.build"] + pass_filenames: false + files: '(^|/)(meson\.build|meson\.options|meson_options\.txt)$' + + - id: clang-format + name: clang-format + language: python + additional_dependencies: ["clang-format==22.1.5"] + entry: python3 tools/format_cpp.py + pass_filenames: false + files: '\.(h|ixx|cpp|md)$' diff --git a/CMakeLists.txt b/CMakeLists.txt index ce01cf2..defa6e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,24 +24,29 @@ if(PROJECT_IS_TOP_LEVEL) ) endif() -target_sources(msft_proxy4 +target_sources( + msft_proxy4 INTERFACE FILE_SET public_headers - TYPE HEADERS - BASE_DIRS include - FILES - include/proxy/proxy.h - include/proxy/proxy_macros.h - include/proxy/proxy_fmt.h - include/proxy/v4/proxy.ixx - include/proxy/v4/proxy.h - include/proxy/v4/proxy_macros.h - include/proxy/v4/proxy_fmt.h + TYPE HEADERS + BASE_DIRS include + FILES + include/proxy/proxy.h + include/proxy/proxy_macros.h + include/proxy/proxy_fmt.h + include/proxy/v4/proxy.ixx + include/proxy/v4/proxy.h + include/proxy/v4/proxy_macros.h + include/proxy/v4/proxy_fmt.h ) target_compile_features(msft_proxy4 INTERFACE cxx_std_20) -target_include_directories(msft_proxy4 INTERFACE $ - $) +target_include_directories( + msft_proxy4 + INTERFACE + $ + $ +) # Do not set the module target if proxy is consumed as a subdirectory. if(PROJECT_IS_TOP_LEVEL) @@ -58,9 +63,10 @@ endif() include(GNUInstallDirs) -install(TARGETS msft_proxy4 - EXPORT msft_proxy4Targets - FILE_SET public_headers DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +install( + TARGETS msft_proxy4 + EXPORT msft_proxy4Targets + FILE_SET public_headers DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) install( @@ -83,19 +89,21 @@ configure_package_config_file( ) include(CMakePackageConfigHelpers) -write_basic_package_version_file(cmake/msft_proxy4ConfigVersion.cmake - COMPATIBILITY SameMajorVersion - ARCH_INDEPENDENT) +write_basic_package_version_file( + cmake/msft_proxy4ConfigVersion.cmake + COMPATIBILITY SameMajorVersion + ARCH_INDEPENDENT +) install( - FILES + FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/msft_proxy4Config.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake/msft_proxy4ConfigVersion.cmake DESTINATION ${CMAKE_INSTALL_DATADIR}/msft_proxy4 ) # build tests if BUILD_TESTING is ON -if (BUILD_TESTING) +if(BUILD_TESTING) include(CTest) include(FetchContent) @@ -108,7 +116,8 @@ if (BUILD_TESTING) FetchContent_Declare( fmt URL https://github.com/fmtlib/fmt/archive/refs/tags/12.1.0.tar.gz - URL_HASH SHA256=ea7de4299689e12b6dddd392f9896f08fb0777ac7168897a244a6d6085043fea + URL_HASH + SHA256=ea7de4299689e12b6dddd392f9896f08fb0777ac7168897a244a6d6085043fea SYSTEM ) FetchContent_MakeAvailable(fmt) @@ -117,7 +126,13 @@ if (BUILD_TESTING) set(PROXY_BUILD_FLAGS /utf-8 /W4) set(PROXY_STRICT_WARNING_FLAGS ${PROXY_BUILD_FLAGS} /WX) else() - set(PROXY_BUILD_FLAGS -Wall -Wextra -Wpedantic $<$:-Wno-c++2b-extensions>) + set( + PROXY_BUILD_FLAGS + -Wall + -Wextra + -Wpedantic + $<$:-Wno-c++2b-extensions> + ) set(PROXY_STRICT_WARNING_FLAGS ${PROXY_BUILD_FLAGS} -Werror) endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4e7cef2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,39 @@ +# Contributing + +Thanks for contributing! This document covers the local lint setup so your changes match what CI enforces. + +## Lint matrix + +CI runs the `bvt-lint` job on every PR. The workflow installs `pre-commit`, runs `pre-commit run --all-files`, and then builds the docs with `mkdocs build --strict`. The full check set, file scopes, and pinned tool versions all live in [`.pre-commit-config.yaml`](.pre-commit-config.yaml). That file is the single source of truth. + +| Hook | What it checks | +|-----------------------|----------------| +| `pre-commit-hooks` | Basic hygiene. Line endings, trailing whitespace, final newline, merge-conflict markers, case-conflicting paths, Windows-illegal names, files over 1024 KB. | +| `ruff-format` | Python scripts (`*.py`). | +| `clang-format` | Every tracked C++ source file (`*.h`, `*.cpp`, `*.ixx`) and the `## Example` cpp blocks inside any Markdown file. | +| `gersemi` | CMake build files (`CMakeLists.txt` and `*.cmake`) outside `subprojects/`. | +| `meson-format` | Meson build files (`meson.build`, `meson.options`, `meson_options.txt`). | +| `buildifier` | Bazel build files (`BUILD.bazel`, `MODULE.bazel`, `WORKSPACE.bazel`, `*.bzl`). | +| `actionlint` | GitHub Actions workflows (`.github/workflows/*.yml`). | + +These hooks run both locally and in CI. On top of them, CI also builds the docs with `mkdocs build --strict` to confirm `docs/` builds cleanly. + +The `clang-format` hook is backed by [`tools/format_cpp.py`](tools/format_cpp.py), which you can also run directly. + +```sh +python3 tools/format_cpp.py # apply fixes in place +python3 tools/format_cpp.py --check # exits non-zero if any file would change +``` + +## Running lint locally + +Install [`pre-commit`](https://pre-commit.com/), then run `pre-commit install` so the hooks run on every `git commit`. The framework manages each formatter at its pinned version for you. See the [pre-commit docs](https://pre-commit.com/) for everyday usage such as running a single hook or all files. + +## Upgrading a formatter + +All versions are pinned in [`.pre-commit-config.yaml`](.pre-commit-config.yaml), which is the only file you change to upgrade a formatter: + +- For most hooks, bump the `rev:` field. +- For local hooks, bump the version pin in `additional_dependencies:` (for example `meson==X.Y.Z` or `clang-format==X.Y.Z`). + +CI reinstalls from the new pin on the next run. diff --git a/MODULE.bazel b/MODULE.bazel index d72167e..72f708e 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -6,13 +6,17 @@ module( bazel_dep(name = "platforms", version = "1.1.0") bazel_dep(name = "rules_cc", version = "0.2.18") bazel_dep(name = "bazel_skylib", version = "1.8.2") + bazel_dep(name = "googletest", version = "1.17.0.bcr.2", dev_dependency = True) bazel_dep(name = "google_benchmark", version = "1.9.5", dev_dependency = True) bazel_dep(name = "fmt", version = "12.1.0", dev_dependency = True) bazel_dep(name = "rules_python", version = "2.0.2", dev_dependency = True) python = use_extension("@rules_python//python/extensions:python.bzl", "python", dev_dependency = True) -python.toolchain(python_version = "3.12", is_default = True) +python.toolchain( + is_default = True, + python_version = "3.12", +) use_repo(python, bootstrap_python = "python_3_12_host") use_repo_rule("//tools:doc_examples.bzl", "doc_examples")(name = "doc_examples") diff --git a/SUPPORT.md b/SUPPORT.md index 50dfa90..bf85080 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -1,16 +1,16 @@ -# Support - -## How to file issues and get help - -This project uses GitHub Issues to track bugs and feature requests. Please search the existing -issues before filing new ones to avoid duplicates. For new issues, file your bug or feature request -as a new issue. - -For help and questions about using this project, please use GitHub Discussions: - -- https://github.com/ngcpp/proxy/discussions - -## Support Policy - -Support for this project is provided on a best-effort basis by the community and maintainers via -the resources listed above. +# Support + +## How to file issues and get help + +This project uses GitHub Issues to track bugs and feature requests. Please search the existing +issues before filing new ones to avoid duplicates. For new issues, file your bug or feature request +as a new issue. + +For help and questions about using this project, please use GitHub Discussions: + +- https://github.com/ngcpp/proxy/discussions + +## Support Policy + +Support for this project is provided on a best-effort basis by the community and maintainers via +the resources listed above. diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 08ccbf8..6843258 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -3,17 +3,35 @@ project(msft_proxy_benchmarks) FetchContent_Declare( benchmark URL https://github.com/google/benchmark/archive/refs/tags/v1.9.5.tar.gz - URL_HASH SHA256=9631341c82bac4a288bef951f8b26b41f69021794184ece969f8473977eaa340 + URL_HASH + SHA256=9631341c82bac4a288bef951f8b26b41f69021794184ece969f8473977eaa340 +) +set( + BENCHMARK_ENABLE_TESTING + OFF + CACHE BOOL + "Disable tests for google benchmark" +) +set( + BENCHMARK_ENABLE_GTEST_TESTS + OFF + CACHE BOOL + "Disable google benchmark unit tests" ) -set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "Disable tests for google benchmark") -set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "Disable google benchmark unit tests") FetchContent_MakeAvailable(benchmark) -add_executable(msft_proxy_benchmarks +add_executable( + msft_proxy_benchmarks proxy_creation_benchmark.cpp proxy_operation_benchmark_context.cpp proxy_operation_benchmark.cpp ) target_include_directories(msft_proxy_benchmarks PRIVATE .) -target_link_libraries(msft_proxy_benchmarks PRIVATE msft_proxy4::proxy benchmark::benchmark benchmark::benchmark_main) -target_compile_options(msft_proxy_benchmarks PRIVATE ${PROXY_BUILD_FLAGS} $<$:/bigobj>) +target_link_libraries( + msft_proxy_benchmarks + PRIVATE msft_proxy4::proxy benchmark::benchmark benchmark::benchmark_main +) +target_compile_options( + msft_proxy_benchmarks + PRIVATE ${PROXY_BUILD_FLAGS} $<$:/bigobj> +) diff --git a/cmake/msft_proxy4ModuleTargets.cmake b/cmake/msft_proxy4ModuleTargets.cmake index ee13002..4846f41 100644 --- a/cmake/msft_proxy4ModuleTargets.cmake +++ b/cmake/msft_proxy4ModuleTargets.cmake @@ -1,27 +1,34 @@ if(NOT DEFINED msft_proxy4_INCLUDE_DIR) - message(FATAL_ERROR "`msft_proxy4_INCLUDE_DIR` must be defined to use this script.") + message( + FATAL_ERROR + "`msft_proxy4_INCLUDE_DIR` must be defined to use this script." + ) endif() -message(STATUS "Declaring `msft_proxy4::proxy_module` target for include path `${msft_proxy4_INCLUDE_DIR}`") +message( + STATUS + "Declaring `msft_proxy4::proxy_module` target for include path `${msft_proxy4_INCLUDE_DIR}`" +) add_library(msft_proxy4_module) set_target_properties( msft_proxy4_module - PROPERTIES - SYSTEM TRUE - EXCLUDE_FROM_ALL TRUE + PROPERTIES SYSTEM TRUE EXCLUDE_FROM_ALL TRUE ) add_library(msft_proxy4::proxy_module ALIAS msft_proxy4_module) -target_sources(msft_proxy4_module PUBLIC - FILE_SET CXX_MODULES - BASE_DIRS ${msft_proxy4_INCLUDE_DIR} - FILES - ${msft_proxy4_INCLUDE_DIR}/proxy/v4/proxy.ixx +target_sources( + msft_proxy4_module + PUBLIC + FILE_SET CXX_MODULES + BASE_DIRS ${msft_proxy4_INCLUDE_DIR} + FILES ${msft_proxy4_INCLUDE_DIR}/proxy/v4/proxy.ixx ) target_compile_features(msft_proxy4_module PUBLIC cxx_std_20) -target_compile_options(msft_proxy4_module PRIVATE - $<$:/utf-8> - $<$:-Wno-c++2b-extensions> +target_compile_options( + msft_proxy4_module + PRIVATE + $<$:/utf-8> + $<$:-Wno-c++2b-extensions> ) target_link_libraries(msft_proxy4_module PUBLIC msft_proxy4::proxy) diff --git a/docs/BUILD.bazel b/docs/BUILD.bazel index 7ecbe9f..b437408 100644 --- a/docs/BUILD.bazel +++ b/docs/BUILD.bazel @@ -1,6 +1,6 @@ +load("@doc_examples//:doc_examples.bzl", "DOC_EXAMPLES") load("@rules_cc//cc:defs.bzl", "cc_test") load("//:copts.bzl", "PROXY_BUILD_COPTS") -load("@doc_examples//:doc_examples.bzl", "DOC_EXAMPLES") package(default_visibility = ["//visibility:private"]) diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index a8697c9..2034fb0 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -9,51 +9,59 @@ file(MAKE_DIRECTORY "${EXAMPLES_DIR}") # Re-run configure when any doc Markdown file or the generator script changes. file(GLOB_RECURSE _MD_FILES "${CMAKE_CURRENT_SOURCE_DIR}/*.md") -set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${_MD_FILES} "${_GEN_SCRIPT}") +set_property( + DIRECTORY + APPEND + PROPERTY CMAKE_CONFIGURE_DEPENDS ${_MD_FILES} "${_GEN_SCRIPT}" +) # Generate the example list at configure time and capture JSON to a variable. execute_process( - COMMAND ${Python3_EXECUTABLE} "${_GEN_SCRIPT}" --format json - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" - OUTPUT_VARIABLE _doc_examples_json - ERROR_VARIABLE _gen_error - RESULT_VARIABLE _gen_result + COMMAND ${Python3_EXECUTABLE} "${_GEN_SCRIPT}" --format json + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + OUTPUT_VARIABLE _doc_examples_json + ERROR_VARIABLE _gen_error + RESULT_VARIABLE _gen_result ) if(_gen_result) - message(FATAL_ERROR "gen_doc_examples.py failed:\n${_gen_error}") + message(FATAL_ERROR "gen_doc_examples.py failed:\n${_gen_error}") endif() # Parse JSON array: [{md: , deps: [, ...]}, ...] string(JSON _len LENGTH "${_doc_examples_json}") math(EXPR _last "${_len} - 1") foreach(i RANGE 0 ${_last}) - string(JSON _md GET "${_doc_examples_json}" ${i} "md") - string(JSON _n_deps LENGTH "${_doc_examples_json}" ${i} "deps") - - set(_dep_names "") - if(_n_deps GREATER 0) - math(EXPR _d_last "${_n_deps} - 1") - foreach(j RANGE 0 ${_d_last}) - string(JSON _dep GET "${_doc_examples_json}" ${i} "deps" ${j}) - list(APPEND _dep_names "${_dep}") - endforeach() - endif() - - string(REPLACE "/" "_" _suffix "${_md}") - string(REPLACE ".md" "" _suffix "${_suffix}") - set(_cpp "${EXAMPLES_DIR}/${_suffix}.cpp") - - add_custom_command( - OUTPUT "${_cpp}" - COMMAND ${Python3_EXECUTABLE} "${_EXTRACT_SCRIPT}" - "${CMAKE_CURRENT_SOURCE_DIR}/${_md}" "${_cpp}" - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${_md}" "${_EXTRACT_SCRIPT}" - VERBATIM - ) - set_source_files_properties("${_cpp}" PROPERTIES GENERATED TRUE) - - add_executable(example_${_suffix} "${_cpp}") - target_link_libraries(example_${_suffix} PRIVATE msft_proxy4::proxy ${_dep_names}) - target_compile_options(example_${_suffix} PRIVATE ${PROXY_BUILD_FLAGS}) - add_test(NAME example_${_suffix} COMMAND example_${_suffix}) + string(JSON _md GET "${_doc_examples_json}" ${i} "md") + string(JSON _n_deps LENGTH "${_doc_examples_json}" ${i} "deps") + + set(_dep_names "") + if(_n_deps GREATER 0) + math(EXPR _d_last "${_n_deps} - 1") + foreach(j RANGE 0 ${_d_last}) + string(JSON _dep GET "${_doc_examples_json}" ${i} "deps" ${j}) + list(APPEND _dep_names "${_dep}") + endforeach() + endif() + + string(REPLACE "/" "_" _suffix "${_md}") + string(REPLACE ".md" "" _suffix "${_suffix}") + set(_cpp "${EXAMPLES_DIR}/${_suffix}.cpp") + + add_custom_command( + OUTPUT "${_cpp}" + COMMAND + ${Python3_EXECUTABLE} "${_EXTRACT_SCRIPT}" + "${CMAKE_CURRENT_SOURCE_DIR}/${_md}" "${_cpp}" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${_md}" "${_EXTRACT_SCRIPT}" + VERBATIM + ) + set_source_files_properties("${_cpp}" PROPERTIES GENERATED TRUE) + + add_executable(example_${_suffix} "${_cpp}") + target_link_libraries( + example_${_suffix} + PRIVATE msft_proxy4::proxy ${_dep_names} + ) + target_compile_options(example_${_suffix} PRIVATE ${PROXY_BUILD_FLAGS}) + add_test(NAME example_${_suffix} COMMAND example_${_suffix}) endforeach() diff --git a/docs/meson.build b/docs/meson.build index 1b3df38..e9b0aea 100644 --- a/docs/meson.build +++ b/docs/meson.build @@ -2,7 +2,12 @@ extract_example = find_program('../tools/extract_example_code.py') gen_doc_examples = find_program('../tools/gen_doc_examples.py') # Generate the example list at configure time (txt format: "" or ":,..."). -_gen = run_command(gen_doc_examples, '--format', 'txt', check: true) +_gen = run_command( + gen_doc_examples, + '--format', + 'txt', + check: true, +) examples = [] @@ -30,7 +35,11 @@ foreach line : _gen.stdout().split('\n') build_by_default: false, ) examples += _ex - test(suffix, _ex, suite: 'ProxyExamples') + test( + suffix, + _ex, + suite: 'ProxyExamples', + ) endif endforeach diff --git a/docs/modules_support.md b/docs/modules_support.md index f24fc5e..00b6370 100644 --- a/docs/modules_support.md +++ b/docs/modules_support.md @@ -91,7 +91,6 @@ int main() { ``` - (1) This is a traditional header rather than a module. It should be declared in global fragment (after `module` and before `export module`). -- (2) This makes all `PRO_DEF_` macros available. This header file contains only some macros and are therefore very fast to compile. +- (2) This makes all `PRO_DEF_` macros available. This header file contains only some macros and are therefore very fast to compile. - (3) `import proxy.v4;` makes all public interfaces from `pro::v4` namespace available in the current translation unit. - (4) As of 2025-05-11, clangd requires the accessor struct to be either `export`-ed, or be declared within an `extern "C++"` block, in order to have auto completion working. - diff --git a/docs/spec/allocate_proxy_shared.md b/docs/spec/allocate_proxy_shared.md index d7490ea..06a9ace 100644 --- a/docs/spec/allocate_proxy_shared.md +++ b/docs/spec/allocate_proxy_shared.md @@ -67,4 +67,4 @@ int main() { ## See Also - [function template `allocate_proxy`](allocate_proxy.md) -- [named requirements *Allocator*](https://en.cppreference.com/w/cpp/named_req/Allocator) \ No newline at end of file +- [named requirements *Allocator*](https://en.cppreference.com/w/cpp/named_req/Allocator) diff --git a/docs/spec/implicit_conversion_dispatch/README.md b/docs/spec/implicit_conversion_dispatch/README.md index fe8a8fa..7b80150 100644 --- a/docs/spec/implicit_conversion_dispatch/README.md +++ b/docs/spec/implicit_conversion_dispatch/README.md @@ -66,4 +66,4 @@ int main() { ## See Also - [class `explicit_conversion_dispatch`](../explicit_conversion_dispatch/README.md) -- [class template `operator_dispatch`](../operator_dispatch/README.md) \ No newline at end of file +- [class template `operator_dispatch`](../operator_dispatch/README.md) diff --git a/docs/spec/make_proxy_view.md b/docs/spec/make_proxy_view.md index 4826350..9a7cae5 100644 --- a/docs/spec/make_proxy_view.md +++ b/docs/spec/make_proxy_view.md @@ -58,4 +58,4 @@ int main() { ## See Also - [concept `proxiable_target`](proxiable_target.md) -- [alias template `skills::as_view`](skills_as_view.md) \ No newline at end of file +- [alias template `skills::as_view`](skills_as_view.md) diff --git a/meson.build b/meson.build index 5538bd0..2eba9f8 100644 --- a/meson.build +++ b/meson.build @@ -24,7 +24,10 @@ elif cxx.get_id() == 'clang' ) elif cxx.get_id() == 'nvidia_hpc' add_project_arguments( - '-Minform=warn', '-Wall', '-Wextra', '-Wpedantic', + '-Minform=warn', + '-Wall', + '-Wextra', + '-Wpedantic', language: 'cpp', ) endif diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 0428551..b1f091b 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -7,8 +7,8 @@ cc_library( name = "test_utils", hdrs = ["utils.h"], includes = ["."], - deps = ["//:proxy"], visibility = [":__pkg__"], + deps = ["//:proxy"], ) cc_test( @@ -33,7 +33,7 @@ cc_test( deps = [ ":test_utils", "//:proxy", - "@fmt//:fmt", + "@fmt", "@googletest//:gtest_main", ], ) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 07580b2..8b6f678 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,14 +3,16 @@ project(msft_proxy_tests) FetchContent_Declare( googletest URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.tar.gz - URL_HASH SHA256=65fab701d9829d38cb77c14acdc431d2108bfdbf8979e40eb8ae567edf10b27c + URL_HASH + SHA256=65fab701d9829d38cb77c14acdc431d2108bfdbf8979e40eb8ae567edf10b27c ) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # For Windows: Prevent overriding the parent project's compiler/linker settings set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) # Disable GMock FetchContent_MakeAvailable(googletest) include(GoogleTest) -add_executable(msft_proxy_tests +add_executable( + msft_proxy_tests proxy_creation_tests.cpp proxy_details_tests.cpp proxy_dispatch_tests.cpp @@ -27,15 +29,34 @@ add_executable(msft_proxy_tests proxy_view_tests.cpp ) target_include_directories(msft_proxy_tests PRIVATE .) -target_link_libraries(msft_proxy_tests PRIVATE msft_proxy4::proxy gtest_main fmt::fmt) +target_link_libraries( + msft_proxy_tests + PRIVATE msft_proxy4::proxy gtest_main fmt::fmt +) target_compile_options(msft_proxy_tests PRIVATE ${PROXY_STRICT_WARNING_FLAGS}) gtest_discover_tests(msft_proxy_tests) -if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - add_executable(msft_proxy_freestanding_tests freestanding/proxy_freestanding_tests.cpp) - target_compile_options(msft_proxy_freestanding_tests PRIVATE -ffreestanding -fno-exceptions -fno-rtti ${PROXY_STRICT_WARNING_FLAGS}) +if( + CMAKE_CXX_COMPILER_ID STREQUAL "GNU" + OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang" +) + add_executable( + msft_proxy_freestanding_tests + freestanding/proxy_freestanding_tests.cpp + ) + target_compile_options( + msft_proxy_freestanding_tests + PRIVATE + -ffreestanding + -fno-exceptions + -fno-rtti + ${PROXY_STRICT_WARNING_FLAGS} + ) target_link_options(msft_proxy_freestanding_tests PRIVATE -nodefaultlibs -lc) - target_link_libraries(msft_proxy_freestanding_tests PRIVATE msft_proxy4::proxy) + target_link_libraries( + msft_proxy_freestanding_tests + PRIVATE msft_proxy4::proxy + ) add_test(NAME ProxyFreestandingTests COMMAND msft_proxy_freestanding_tests) endif() diff --git a/tests/modules/CMakeLists.txt b/tests/modules/CMakeLists.txt index 5419eca..0183f81 100644 --- a/tests/modules/CMakeLists.txt +++ b/tests/modules/CMakeLists.txt @@ -1,14 +1,8 @@ - if(PROXY_BUILD_MODULES) - target_sources(msft_proxy_tests - PRIVATE - main.cpp - ) - target_sources(msft_proxy_tests - PRIVATE - FILE_SET CXX_MODULES FILES - foo.ixx - impl.ixx + target_sources(msft_proxy_tests PRIVATE main.cpp) + target_sources( + msft_proxy_tests + PRIVATE FILE_SET CXX_MODULES FILES foo.ixx impl.ixx ) target_link_libraries(msft_proxy_tests PRIVATE msft_proxy4::proxy_module) endif() diff --git a/tools/extract_example_code.py b/tools/extract_example_code.py index 87a4d77..608399f 100644 --- a/tools/extract_example_code.py +++ b/tools/extract_example_code.py @@ -11,29 +11,36 @@ from pathlib import Path from typing import Optional +_EXAMPLE_PATTERN = re.compile( + r"(?P## Example\r?\n\r?\n```cpp\r?\n)" + r"(?P.*?)" + r"(?P\r?\n```)", + re.DOTALL, +) -def try_extract_example_code(md_path: Path) -> Optional[str]: - """Return the generated C++ source for *md_path*, or None if it has no example.""" - example_pattern = re.compile( - r"## Example\r?\n\r?\n```cpp\r?\n(.*?)\r?\n```", re.DOTALL - ) - with open(md_path, "r", encoding="utf-8") as f: - content = f.read() - blocks: list[str] = re.findall(example_pattern, content) - if len(blocks) == 0: - return None - if len(blocks) > 1: - raise ValueError(f"'{md_path}' has more than one '## Example' C++ block.") +def try_extract_example_code(content: str) -> Optional["re.Match[str]"]: + """Return the sole ## Example cpp block match in *content*, or None. - code = blocks[0] - return f"// This file was auto-generated from:\n// {md_path}\n\n{code}" + Raises ValueError if more than one such block exists. + """ + matches = list(_EXAMPLE_PATTERN.finditer(content)) + if len(matches) > 1: + raise ValueError("more than one '## Example' C++ block") + return matches[0] if matches else None if __name__ == "__main__": if len(sys.argv) != 3: print(f"Usage: {sys.argv[0]} INPUT.md OUTPUT.cpp", file=sys.stderr) sys.exit(1) - code = try_extract_example_code(Path(sys.argv[1])) - if code is not None: + md_path = Path(sys.argv[1]) + try: + m = try_extract_example_code(md_path.read_text(encoding="utf-8")) + except ValueError as e: + raise ValueError(f"'{md_path}': {e}") from None + if m is not None: + code = ( + f"// This file was auto-generated from:\n// {md_path}\n\n{m.group('code')}" + ) Path(sys.argv[2]).write_text(code, encoding="utf-8") diff --git a/tools/format_cpp.py b/tools/format_cpp.py new file mode 100755 index 0000000..2ba12eb --- /dev/null +++ b/tools/format_cpp.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# pyright: strict + +"""Format C++ throughout the repo. + +Usage: + format_cpp.py Reformat every C++ file (and Markdown example) in place. + format_cpp.py --check Don't change anything. Exit non-zero if any file + is not already formatted. + +Requires `clang-format` on PATH. The pre-commit hook supplies one via the +`clang-format` PyPI wheel. Manual invocations can use any system install. +""" + +from __future__ import annotations + +import argparse +import subprocess +import sys +from pathlib import Path + +from extract_example_code import try_extract_example_code + +REPO_ROOT = Path(__file__).resolve().parent.parent + + +def _git_ls(*patterns: str) -> list[Path]: + result = subprocess.run( + [ + "git", + "ls-files", + "--cached", + "--others", + "--exclude-standard", + "-z", + "--", + *patterns, + ], + cwd=REPO_ROOT, + capture_output=True, + text=True, + check=True, + ) + return sorted(REPO_ROOT / p for p in result.stdout.split("\0") if p) + + +def _format_stdin(code: str) -> str: + result = subprocess.run( + ["clang-format", "--assume-filename=example.cpp"], + input=code, + text=True, + capture_output=True, + check=True, + ) + return result.stdout + + +def _format_static(files: list[Path], *, check: bool) -> bool: + if not files: + return True + args = [str(f) for f in files] + cmd = ( + ["clang-format", "--dry-run", "--Werror", *args] + if check + else ["clang-format", "-i", *args] + ) + return subprocess.run(cmd, cwd=REPO_ROOT).returncode == 0 + + +def _format_markdown(md: Path, *, check: bool) -> bool: + content = md.read_text(encoding="utf-8") + try: + m = try_extract_example_code(content) + except ValueError as e: + raise ValueError(f"'{md.relative_to(REPO_ROOT)}': {e}") from None + if m is None: + return True + + original = m.group("code") + formatted = _format_stdin(original).rstrip("\n") + if formatted == original: + return True + + if check: + rel = md.relative_to(REPO_ROOT).as_posix() + print(f"{rel}: '## Example' block would be reformatted", file=sys.stderr) + return False + + new_content = content[: m.start("code")] + formatted + content[m.end("code") :] + md.write_text(new_content, encoding="utf-8") + return True + + +def main() -> int: + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument( + "--check", + action="store_true", + help="dry-run; exit non-zero if anything would change", + ) + args = parser.parse_args() + + ok = _format_static(_git_ls("*.h", "*.cpp", "*.ixx"), check=args.check) + for md in _git_ls("*.md"): + ok = _format_markdown(md, check=args.check) and ok + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/gen_doc_examples.py b/tools/gen_doc_examples.py index 0f660f4..2bcf6e9 100644 --- a/tools/gen_doc_examples.py +++ b/tools/gen_doc_examples.py @@ -32,7 +32,7 @@ class DepInfo: @dataclass(frozen=True) class Example: - rel_md: str # path relative to docs/, e.g. "spec/allocate_proxy.md" + rel_md: str # path relative to docs/, e.g. "spec/allocate_proxy.md" deps: tuple[str, ...] # logical dep names, e.g. ("fmt",) @@ -41,7 +41,7 @@ class Example: # Add one entry here when introducing a new library dependency. _DEPS: dict[str, DepInfo] = { - "fmt": DepInfo(cmake="fmt::fmt", meson="fmt_dep", bazel="@fmt//:fmt"), + "fmt": DepInfo(cmake="fmt::fmt", meson="fmt_dep", bazel="@fmt"), } # Hardcoded per-file dependencies (path relative to docs/). @@ -49,10 +49,11 @@ class Example: "spec/skills_fmt_format.md": ("fmt",), } + def _collect(docs_dir: Path) -> list[Example]: examples: list[Example] = [] for md in sorted(docs_dir.rglob("*.md")): - if try_extract_example_code(md) is None: + if try_extract_example_code(md.read_text(encoding="utf-8")) is None: continue rel_md = md.relative_to(docs_dir).as_posix() examples.append(Example(rel_md=rel_md, deps=_FILE_DEPS.get(rel_md, ()))) @@ -60,13 +61,25 @@ def _collect(docs_dir: Path) -> list[Example]: def _generate_json(examples: list[Example]) -> str: - return json.dumps([{"md": e.rel_md, "deps": [_DEPS[d].cmake for d in e.deps]} for e in examples]) + "\n" + return ( + json.dumps( + [ + {"md": e.rel_md, "deps": [_DEPS[d].cmake for d in e.deps]} + for e in examples + ] + ) + + "\n" + ) def _generate_txt(examples: list[Example]) -> str: lines: list[str] = [] for e in examples: - lines.append(f"{e.rel_md}:{','.join(_DEPS[d].meson for d in e.deps)}" if e.deps else e.rel_md) + lines.append( + f"{e.rel_md}:{','.join(_DEPS[d].meson for d in e.deps)}" + if e.deps + else e.rel_md + ) return "\n".join(lines) + "\n" diff --git a/tools/report_generator/CMakeLists.txt b/tools/report_generator/CMakeLists.txt index 63eda32..9436c13 100644 --- a/tools/report_generator/CMakeLists.txt +++ b/tools/report_generator/CMakeLists.txt @@ -12,7 +12,8 @@ endif() FetchContent_Declare( nlohmann_json URL https://github.com/nlohmann/json/archive/refs/tags/v3.12.0.tar.gz - URL_HASH SHA256=4b92eb0c06d10683f7447ce9406cb97cd4b453be18d7279320f7b2f025c10187 + URL_HASH + SHA256=4b92eb0c06d10683f7447ce9406cb97cd4b453be18d7279320f7b2f025c10187 ) FetchContent_MakeAvailable(nlohmann_json) @@ -21,8 +22,11 @@ add_executable(report_generator main.cpp) target_link_libraries(report_generator PRIVATE nlohmann_json::nlohmann_json) target_compile_features(report_generator PRIVATE cxx_std_20) -if (MSVC) +if(MSVC) target_compile_options(report_generator PRIVATE /W4 /WX) else() - target_compile_options(report_generator PRIVATE -Wall -Wextra -Wpedantic -Werror) + target_compile_options( + report_generator + PRIVATE -Wall -Wextra -Wpedantic -Werror + ) endif() diff --git a/tools/report_generator/report-config.json b/tools/report_generator/report-config.json index adcb22f..f9023c0 100644 --- a/tools/report_generator/report-config.json +++ b/tools/report_generator/report-config.json @@ -184,4 +184,4 @@ ] } ] -} \ No newline at end of file +}