From 25af5716f5a0d74aa58eab56b671959e1af86c1a Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Fri, 14 Aug 2026 15:47:43 +0000 Subject: [PATCH 1/3] Enable interface header self-containment verification in CI Turn the dormant VERIFY_INTERFACE_HEADER_SETS check (all_verify_interface_header_sets, excluded from the default ALL target by CMake design) into an explicit CI step, run once in the plain clang 22 Debug build. A handful of headers are not self-contained by design and are excluded from verification accordingly: - __detail/__epilogue.hpp pops warning/pragma state that __detail/__prologue.hpp is documented to push, so it cannot compile standalone. - __detail/__parallel_scheduler_default_impl_entry.hpp has a documented precondition that includers define STDEXEC_PARALLEL_SCHEDULER_INLINE first. - exec/tbb/tbb_thread_pool.hpp and exec/taskflow/taskflow_thread_pool.hpp unconditionally include an optional external dependency; only verify them when the corresponding STDEXEC_ENABLE_TBB / STDEXEC_ENABLE_TASKFLOW option is on. - exec/windows/filetime_clock.hpp and exec/windows/windows_thread_pool.hpp are Windows-only. Verified locally (gcc 13, ASIO via Boost, TBB/Taskflow off, non-Windows): before this change, all_verify_interface_header_sets reports 7 failures against main, 6 of which are the by-design cases above; the 7th is __detail/__when_all.hpp's missing dependency on __just.hpp, fixed separately in #2195. With this change applied, the by-design failures are gone and __when_all.hpp is the only remaining failure until #2195 lands; applying #2195's fix on top of this branch yields a fully clean run (178/178). --- .github/workflows/ci.cpu.yml | 8 ++++++++ CMakeLists.txt | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/.github/workflows/ci.cpu.yml b/.github/workflows/ci.cpu.yml index b4b17d312..94b9a2181 100644 --- a/.github/workflows/ci.cpu.yml +++ b/.github/workflows/ci.cpu.yml @@ -128,6 +128,14 @@ jobs: # to result in fewer Clang ICEs. cmake --build build -v -j ${{ contains(matrix.name, 'modules') && 1 || 512 }}; + # Verify that public headers are self-contained. Header + # verification is independent of the rest of the build matrix, so + # it only needs to run once; pick a single plain (non-modules, + # non-sanitizer) Debug build to run it in. + if [ "${{ matrix.name }}" = "CPU (clang 22, Debug)" ]; then + cmake --build build -v --target all_verify_interface_header_sets; + fi + # Print sccache stats sccache -s; diff --git a/CMakeLists.txt b/CMakeLists.txt index 508ae9fc1..1f402742a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -262,6 +262,40 @@ set_target_properties(stdexec PROPERTIES VERSION "${STDEXEC_VERSION}" if(STDEXEC_BUILD_TESTS) # Test that headers are self-contained set_target_properties(stdexec PROPERTIES VERIFY_INTERFACE_HEADER_SETS TRUE) + + # A handful of headers are not self-contained by design; exclude them from + # verification rather than treating that as a bug. + + # These headers bracket a translation unit's warning/pragma state. + # __epilogue.hpp pops state that __prologue.hpp is documented to push, so it + # cannot compile on its own. + set_source_files_properties(include/stdexec/__detail/__epilogue.hpp + PROPERTIES SKIP_LINTING ON) + + # This header has a documented precondition that includers define + # STDEXEC_PARALLEL_SCHEDULER_INLINE before including it. + set_source_files_properties( + include/stdexec/__detail/__parallel_scheduler_default_impl_entry.hpp + PROPERTIES SKIP_LINTING ON) + + # These headers require an optional external dependency that isn't present + # in every configuration; only verify them when the corresponding feature + # is actually enabled. + if(NOT STDEXEC_ENABLE_TBB) + set_source_files_properties(include/exec/tbb/tbb_thread_pool.hpp + PROPERTIES SKIP_LINTING ON) + endif() + if(NOT STDEXEC_ENABLE_TASKFLOW) + set_source_files_properties(include/exec/taskflow/taskflow_thread_pool.hpp + PROPERTIES SKIP_LINTING ON) + endif() + + # These headers are Windows-only. + if(NOT WIN32) + set_source_files_properties( + include/exec/windows/filetime_clock.hpp + include/exec/windows/windows_thread_pool.hpp PROPERTIES SKIP_LINTING ON) + endif() endif() # Declare the public include directories From 5698323150f92bc2f25536043b21980eaf26f5da Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Fri, 14 Aug 2026 15:51:06 +0000 Subject: [PATCH 2/3] Add hand-written self-containment check for __parallel_scheduler_default_impl_entry.hpp __detail/__parallel_scheduler_default_impl_entry.hpp is excluded from VERIFY_INTERFACE_HEADER_SETS because it has a documented precondition (includers must define STDEXEC_PARALLEL_SCHEDULER_INLINE before including it) that the automatic verification can't express. The only place that precondition is otherwise exercised is src/parallel_scheduler/ parallel_scheduler.cpp, which is gated behind STDEXEC_BUILD_PARALLEL_SCHEDULER (default OFF, never enabled in CI), so as of the previous commit this header had no coverage in CI at all. Add a small OBJECT-library translation unit that defines STDEXEC_PARALLEL_SCHEDULER_INLINE and includes the header, so a regression still fails a normal build. Tried wiring this in as an extra dependency of CMake's auto-generated all_verify_interface_header_sets target instead, but that target doesn't exist yet at the point test/CMakeLists.txt runs (checked directly with if(TARGET ...)); it's created later, once the whole build has been configured. A plain always-built OBJECT library sidesteps that and costs one extra small translation unit per build. Verified locally: builds clean as-is; deliberately removing the STDEXEC_PARALLEL_SCHEDULER_INLINE definition reproduces the expected 'must be defined before including this header' compile error, confirming the check actually catches a regression. --- test/CMakeLists.txt | 23 +++++++++++++ ..._parallel_scheduler_default_impl_entry.cpp | 32 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 test/stdexec/detail/verify_parallel_scheduler_default_impl_entry.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2725a8fba..5c2efefd8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -174,6 +174,29 @@ if(STDEXEC_BUILD_PARALLEL_SCHEDULER AND NOT STDEXEC_ENABLE_CUDA) 30) endif() +# __detail/__parallel_scheduler_default_impl_entry.hpp is excluded from +# VERIFY_INTERFACE_HEADER_SETS because it has a documented precondition +# (includers must define STDEXEC_PARALLEL_SCHEDULER_INLINE first) that the +# automatic verification can't express. Compile it here instead, with that +# precondition satisfied. +# +# This is a plain (non-EXCLUDE_FROM_ALL) OBJECT library rather than a +# dependency hooked onto all_verify_interface_header_sets: CMake creates that +# aggregate target itself only once the whole build has been configured +# (confirmed directly - `if(TARGET all_verify_interface_header_sets)` is +# still false at this point in test/CMakeLists.txt, even though the property +# enabling it was set earlier in the root CMakeLists.txt), so there's no +# handle to attach a dependency to from here. Building it unconditionally is +# simpler and just as effective: it's one small translation unit, so the +# added cost per build is negligible. +add_library(verify_parallel_scheduler_default_impl_entry OBJECT + stdexec/detail/verify_parallel_scheduler_default_impl_entry.cpp) +target_link_libraries(verify_parallel_scheduler_default_impl_entry + PRIVATE STDEXEC::stdexec) +set_target_properties( + verify_parallel_scheduler_default_impl_entry + PROPERTIES CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF) + add_subdirectory(exec) if(STDEXEC_ENABLE_CUDA) diff --git a/test/stdexec/detail/verify_parallel_scheduler_default_impl_entry.cpp b/test/stdexec/detail/verify_parallel_scheduler_default_impl_entry.cpp new file mode 100644 index 000000000..04196f777 --- /dev/null +++ b/test/stdexec/detail/verify_parallel_scheduler_default_impl_entry.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026 NVIDIA Corporation + * + * Licensed under the Apache License Version 2.0 with LLVM Exceptions + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://llvm.org/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// stdexec/__detail/__parallel_scheduler_default_impl_entry.hpp is excluded +// from VERIFY_INTERFACE_HEADER_SETS (see the root CMakeLists.txt) because it +// has a documented precondition: includers must define +// STDEXEC_PARALLEL_SCHEDULER_INLINE before including it. That precondition +// is otherwise only exercised when STDEXEC_BUILD_PARALLEL_SCHEDULER is +// enabled, which CI does not currently do. +// +// This translation unit exists solely to verify that, given the documented +// precondition, the header is otherwise self-contained. It is not linked +// into any test executable or library; see the verify_parallel_scheduler_ +// default_impl_entry OBJECT library target in this directory's CMakeLists +// wiring, which is hooked into all_verify_interface_header_sets as an extra +// dependency. + +#define STDEXEC_PARALLEL_SCHEDULER_INLINE inline +#include "stdexec/__detail/__parallel_scheduler_default_impl_entry.hpp" From 637d5ca96f9fd68233203c4af7c99b32005cb657 Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Sat, 15 Aug 2026 18:55:16 +0000 Subject: [PATCH 3/3] Actually exercise every conditionally-excluded header somewhere in CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ian's review comment on the CI step (added in 25af571): the claim that header verification 'is independent of the rest of the build matrix, so it only needs to run once' is wrong for the conditionally-excluded headers specifically. Running the check only on the Linux clang-22 Debug job meant exec/windows/filetime_clock.hpp, exec/windows/windows_thread_pool.hpp, and exec/taskflow/taskflow_thread_pool.hpp were SKIP_LINTING'd there (correctly, per their own guards) but never compiled anywhere else in CI either, so they had no coverage at all despite the PR's stated goal. - Force-enable STDEXEC_ENABLE_TASKFLOW on the same clang-22 Debug job that already runs the verification target, alongside the existing STDEXEC_ENABLE_TBB. Verified locally that this is a safe, self-contained CPM fetch (same mechanism as the existing Boost/ASIO dependency) and that all_verify_interface_header_sets goes from 178 to 179 checked headers with it on, all passing. - Add the equivalent verification step to test-windows.ps1, gated to Debug configs so it doesn't run twice per compiler/toolset matrix entry. This is the only place in CI where WIN32 is true, so it's the only place the two Windows-only headers actually get compiled and checked. Not locally verifiable — no Windows toolchain available in this environment — but the change follows the existing script's structure and conventions closely. Between the two, every SKIP_LINTING exclusion added in 1e7de1a now has somewhere in CI that actually compiles it: TBB and Taskflow on the Linux job, the two Windows-only headers on the Windows Debug jobs. __epilogue.hpp and __parallel_scheduler_default_impl_entry.hpp were already covered unconditionally (the former isn't gated at all; the latter has its own hand-written check from 5698323). --- .github/workflows/ci.cpu.yml | 18 ++++++++++++++---- .github/workflows/test-windows.ps1 | 11 +++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.cpu.yml b/.github/workflows/ci.cpu.yml index 94b9a2181..9677fe15d 100644 --- a/.github/workflows/ci.cpu.yml +++ b/.github/workflows/ci.cpu.yml @@ -113,6 +113,7 @@ jobs: -DCMAKE_BUILD_TYPE=${{ matrix.build }} \ -DCMAKE_CXX_FLAGS="${{ matrix.cxxflags }}" \ -DSTDEXEC_ENABLE_TBB:BOOL=${{ !contains(matrix.cxxflags, '-fsanitize') && !contains(matrix.name, 'modules') }} \ + -DSTDEXEC_ENABLE_TASKFLOW:BOOL=${{ matrix.name == 'CPU (clang 22, Debug)' }} \ -DSTDEXEC_ENABLE_ASIO:BOOL=TRUE \ -DSTDEXEC_ASIO_IMPLEMENTATION:STRING=boost \ -DCMAKE_CXX_STANDARD:STRING=${{ matrix.cxxstd }} \ @@ -128,10 +129,19 @@ jobs: # to result in fewer Clang ICEs. cmake --build build -v -j ${{ contains(matrix.name, 'modules') && 1 || 512 }}; - # Verify that public headers are self-contained. Header - # verification is independent of the rest of the build matrix, so - # it only needs to run once; pick a single plain (non-modules, - # non-sanitizer) Debug build to run it in. + # Verify that public headers are self-contained. This alone doesn't + # depend on the rest of the build matrix, but the SKIP_LINTING + # exclusions in CMakeLists.txt for optional-dependency and + # platform-specific headers do: run it only here and it would + # silently never compile exec/taskflow/taskflow_thread_pool.hpp, + # exec/windows/filetime_clock.hpp, or exec/windows/windows_thread_pool.hpp + # anywhere in CI, defeating the point of checking them at all. So: + # Taskflow is force-enabled above just for this one job (verified + # locally that STDEXEC_ENABLE_TASKFLOW builds clean via CPM, same as + # the existing Boost/ASIO fetch), which covers the TBB and Taskflow + # exclusions between them; the Windows-only headers are covered + # separately by the equivalent step added to test-windows.ps1, which + # runs on an actual Windows job where WIN32 is true. if [ "${{ matrix.name }}" = "CPU (clang 22, Debug)" ]; then cmake --build build -v --target all_verify_interface_header_sets; fi diff --git a/.github/workflows/test-windows.ps1 b/.github/workflows/test-windows.ps1 index c20ecc521..8edbf87a2 100644 --- a/.github/workflows/test-windows.ps1 +++ b/.github/workflows/test-windows.ps1 @@ -28,4 +28,15 @@ Invoke-NativeCommand cmake -B $BuildDirectory -G Ninja ` "-DSTDEXEC_ASIO_IMPLEMENTATION:STRING=boost" ` "-DSTDEXEC_BUILD_TESTS:BOOL=TRUE" . Invoke-NativeCommand cmake --build $BuildDirectory + +# Verify that public headers are self-contained, same as the Linux CI job. +# This is the only job in CI where WIN32 is true, so it's the only place +# exec/windows/filetime_clock.hpp and exec/windows/windows_thread_pool.hpp +# (SKIP_LINTING'd everywhere else per CMakeLists.txt) actually get compiled +# and checked. Gated to Debug so it doesn't run twice per compiler/toolset +# pairing, matching the "run once" approach on the Linux side. +if ($Config -eq "Debug") { + Invoke-NativeCommand cmake --build $BuildDirectory --target all_verify_interface_header_sets +} + Invoke-NativeCommand ctest --test-dir $BuildDirectory --output-on-failure --verbose --timeout 60