-
Notifications
You must be signed in to change notification settings - Fork 250
CI: Add WinAPI header compatibility workflow #2630
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
annihilatorq
merged 17 commits into
stephenberry:main
from
annihilatorq:ci/windows-header-compatibility-workflow
Jun 18, 2026
+257
−0
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
eacf8bf
ci(windows): add <Windows.h> compatibility workflow
annihilatorq 36ceb50
fix(ci): use a single target for all glaze includes
annihilatorq d6d9835
ci: switch Windows header compatibility workflow to windows-latest
annihilatorq 6d23e80
fix(ci): compile glaze/eetf headers with installed erlang
annihilatorq c37ecd2
fix(ci): specify OS verison for erlang setup
annihilatorq 718eb85
fix(ci): install asio and eigen via vcpkg
annihilatorq 1da9f17
fix(ci): build glaze/net headers with WIN32_LEAN_AND_MEAN
annihilatorq b79a173
fix(ci): define ssize_t for erlang headers on Windows
annihilatorq dbeff21
fix(ci): split winsock.h-sensitive header compatibility checks
annihilatorq ad7feb2
fix(ci): remove /Zs compile option
annihilatorq 2e9d354
refactor(ci): move windows-header-compatibility CMake to cmake/ci dir…
annihilatorq 0c40575
refactor(ci): make CMake code and workflow more readable
annihilatorq dc9d3bc
chore(ci): remove redundant CMAKE_CXX_STANDARD option
annihilatorq 2a8e664
chore(ci): remove redundant vcpkg triplet selection
annihilatorq 32570fc
chore(ci): remove redundant CMake architecture option
annihilatorq b9548f2
fix(ci): vcpkg installation typo
annihilatorq 9c4df1b
chore(ci): remove redundant vcpkg triplet in CMake configuration
annihilatorq File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| # This workflow verifies that Glaze headers can be compiled after including | ||
| # <Windows.h> in an unsanitized Windows project. | ||
| # | ||
| # NOMINMAX and WIN32_LEAN_AND_MEAN are manually disabled before including <Windows.h> | ||
| # The workflow generates two aggregate translation units that recursively include | ||
| # every .hpp file inside include/glaze. This will catch collisions with Windows | ||
| # macros such as 'min', 'max', 'ERROR', 'near', 'far', and 'small', and will also | ||
| # prevent fixes that silently mutate the user's macro environment by undefining | ||
| # those macros. | ||
| # | ||
| # DELETE is a special case for 'net' which undefs it, so this macro is not checked. | ||
| # Headers that depend on Winsock2 are compiled separately with WIN32_LEAN_AND_MEAN | ||
| # because Windows.h otherwise includes Winsock.h, which conflicts with Winsock2.h. | ||
|
|
||
| name: windows-header-compatibility | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
| - feature/* | ||
| paths: | ||
| - 'include/**' | ||
| - 'cmake/**' | ||
| - 'CMakeLists.txt' | ||
| - '.github/workflows/windows-header-compatibility.yml' | ||
| pull_request: | ||
| branches: | ||
| - main | ||
| paths: | ||
| - 'include/**' | ||
| - 'cmake/**' | ||
| - 'CMakeLists.txt' | ||
| - '.github/workflows/windows-header-compatibility.yml' | ||
| workflow_dispatch: | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: windows-latest | ||
| timeout-minutes: 45 | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v6 | ||
|
|
||
| - uses: erlef/setup-beam@v1.24.0 | ||
| env: | ||
| ImageOS: win25 | ||
| with: | ||
| otp-version: '28.5.0.2' | ||
| version-type: strict | ||
|
|
||
| - name: Install vcpkg dependencies | ||
| run: '& "$env:VCPKG_INSTALLATION_ROOT\vcpkg.exe" install asio eigen3' | ||
|
|
||
| - name: Configure CMake | ||
| run: | | ||
| cmake -S "$env:GITHUB_WORKSPACE/cmake/ci/windows-header-compatibility" ` | ||
| -B "$env:GITHUB_WORKSPACE/build/windows-header-compatibility/build" ` | ||
| -DGLAZE_SOURCE_DIR="$env:GITHUB_WORKSPACE" ` | ||
| -DERLANG_OTP_DIR="$env:INSTALL_DIR_FOR_OTP" ` | ||
| -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" ` | ||
|
|
||
| - name: Build | ||
| run: cmake --build "$env:GITHUB_WORKSPACE/build/windows-header-compatibility/build" --config Debug --target glaze_windows_header_compatibility --parallel | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,193 @@ | ||
| cmake_minimum_required(VERSION 3.21) | ||
|
|
||
| project(glaze_windows_header_compatibility LANGUAGES CXX) | ||
|
|
||
| set(GLAZE_SOURCE_DIR "" CACHE PATH "Glaze source directory") | ||
| set(ERLANG_OTP_DIR "" CACHE PATH "Erlang/OTP installation directory") | ||
|
|
||
| if(NOT GLAZE_SOURCE_DIR) | ||
| message(FATAL_ERROR "GLAZE_SOURCE_DIR is required") | ||
| endif() | ||
|
|
||
| if(NOT ERLANG_OTP_DIR) | ||
| message(FATAL_ERROR "ERLANG_OTP_DIR is required") | ||
| endif() | ||
|
|
||
| get_filename_component(GLAZE_SOURCE_DIR "${GLAZE_SOURCE_DIR}" ABSOLUTE) | ||
| get_filename_component(ERLANG_OTP_DIR "${ERLANG_OTP_DIR}" ABSOLUTE) | ||
|
|
||
| if(NOT EXISTS "${GLAZE_SOURCE_DIR}/CMakeLists.txt") | ||
| message(FATAL_ERROR "GLAZE_SOURCE_DIR must point to the Glaze source tree") | ||
| endif() | ||
|
|
||
| set(ERLANG_EI_INCLUDE_DIR "${ERLANG_OTP_DIR}/usr/include") | ||
|
|
||
| if(NOT EXISTS "${ERLANG_EI_INCLUDE_DIR}/ei.h") | ||
| message(FATAL_ERROR "ei.h was not found at ${ERLANG_EI_INCLUDE_DIR}") | ||
| endif() | ||
|
|
||
| message(STATUS "Using Erlang ei include directory: ${ERLANG_EI_INCLUDE_DIR}") | ||
|
|
||
| add_subdirectory("${GLAZE_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/glaze") | ||
|
|
||
| file(GLOB_RECURSE glaze_public_headers CONFIGURE_DEPENDS RELATIVE "${GLAZE_SOURCE_DIR}/include" | ||
| "${GLAZE_SOURCE_DIR}/include/glaze/*.hpp" | ||
| ) | ||
|
|
||
| # Headers that include Winsock2 must be tested with WIN32_LEAN_AND_MEAN, | ||
| # otherwise Windows.h includes Winsock.h first | ||
| set(glaze_windows_lean_header_globs | ||
| "glaze/net/*.hpp" | ||
| "glaze/rpc/*.hpp" | ||
| ) | ||
| set(glaze_windows_lean_headers | ||
| "glaze/ext/glaze_asio.hpp" | ||
| "glaze/file/hostname_include.hpp" | ||
| ) | ||
|
|
||
| # Append all headers from the Winsock2-sensitive directories | ||
| foreach(header IN LISTS glaze_windows_lean_header_globs) | ||
| file(GLOB_RECURSE glaze_windows_lean_header_matches CONFIGURE_DEPENDS RELATIVE "${GLAZE_SOURCE_DIR}/include" | ||
| "${GLAZE_SOURCE_DIR}/include/${header}" | ||
| ) | ||
| list(APPEND glaze_windows_lean_headers ${glaze_windows_lean_header_matches}) | ||
| endforeach() | ||
|
|
||
| # Everything starts in the unsanitized Windows.h group | ||
| set(glaze_windows_headers "${glaze_public_headers}") | ||
|
|
||
| # Remove headers intended for WIN32_LEAN_AND_MEAN build from regular headers | ||
| list(REMOVE_ITEM glaze_windows_headers ${glaze_windows_lean_headers}) | ||
|
|
||
| list(REMOVE_DUPLICATES glaze_windows_lean_headers) | ||
| list(SORT glaze_public_headers) | ||
| list(SORT glaze_windows_headers) | ||
| list(SORT glaze_windows_lean_headers) | ||
|
|
||
| # Include registry.hpp before headers that specialize its templates | ||
| set(glaze_windows_lean_first_headers | ||
| "glaze/rpc/registry.hpp" | ||
| ) | ||
| list(REMOVE_ITEM glaze_windows_lean_headers ${glaze_windows_lean_first_headers}) | ||
| list(PREPEND glaze_windows_lean_headers ${glaze_windows_lean_first_headers}) | ||
|
|
||
| # Counting headers is needed only for CI logs | ||
| list(LENGTH glaze_public_headers glaze_public_header_count) | ||
| list(LENGTH glaze_windows_headers glaze_windows_header_count) | ||
| list(LENGTH glaze_windows_lean_headers glaze_windows_lean_header_count) | ||
|
|
||
| if(glaze_public_header_count EQUAL 0) | ||
| message(FATAL_ERROR "No Glaze public headers were found") | ||
| endif() | ||
|
|
||
| message(STATUS "Found ${glaze_public_header_count} Glaze public headers") | ||
| message(STATUS "Using unsanitized Windows.h for ${glaze_windows_header_count} Glaze public headers") | ||
| message(STATUS "Using WIN32_LEAN_AND_MEAN Windows.h for ${glaze_windows_lean_header_count} Glaze public headers") | ||
|
|
||
| # Checks that Glaze code haven't accidentally #undef-ed any Windows macro | ||
| function(append_windows_macro_asserts content_var error_context require_delete require_small) | ||
| set(macro_names min max ERROR near far) | ||
|
|
||
| if(require_delete) | ||
| set(macro_names min max ERROR DELETE near far) | ||
| endif() | ||
|
|
||
| foreach(macro_name IN LISTS macro_names) | ||
| string(APPEND ${content_var} | ||
| "#ifndef ${macro_name}\n" | ||
| "#error ${macro_name} macro ${error_context}\n" | ||
| "#endif\n" | ||
| ) | ||
| endforeach() | ||
|
|
||
| if(require_small) | ||
| string(APPEND ${content_var} | ||
| "#ifdef GLAZE_WINDOWS_HEADER_HAS_SMALL\n" | ||
| "#ifndef small\n" | ||
| "#error small macro ${error_context}\n" | ||
| "#endif\n" | ||
| "#endif\n" | ||
| ) | ||
| endif() | ||
|
|
||
| set(${content_var} "${${content_var}}" PARENT_SCOPE) | ||
| endfunction() | ||
|
|
||
| function(append_windows_prelude content_var use_win32_lean_and_mean) | ||
| set(win32_lean_and_mean_line "#undef WIN32_LEAN_AND_MEAN") | ||
|
|
||
| # WIN32_LEAN_AND_MEAN avoids Winsock.h so Winsock2.h can be included later | ||
| if(use_win32_lean_and_mean) | ||
| set(win32_lean_and_mean_line "#define WIN32_LEAN_AND_MEAN") | ||
| endif() | ||
|
|
||
| string(APPEND ${content_var} | ||
| "#ifdef _WINDOWS_\n" | ||
| "#error Windows.h must not be included before this compatibility source\n" | ||
| "#endif\n" | ||
| "#undef NOMINMAX\n" | ||
| "${win32_lean_and_mean_line}\n" | ||
| "#undef NOGDI\n" | ||
| "#include <Windows.h>\n" | ||
| "#include <BaseTsd.h>\n" | ||
| "#if !defined(_SSIZE_T_DEFINED)\n" | ||
| "using ssize_t = SSIZE_T;\n" | ||
| "#define _SSIZE_T_DEFINED\n" | ||
| "#endif\n" | ||
| ) | ||
|
|
||
| # Verify that none of the Windows macros was #undef-ed by Glaze code | ||
| append_windows_macro_asserts(${content_var} "is expected after including Windows.h" TRUE FALSE) | ||
|
|
||
| string(APPEND ${content_var} | ||
| "#ifdef small\n" | ||
| "#define GLAZE_WINDOWS_HEADER_HAS_SMALL 1\n" | ||
| "#endif\n" | ||
| ) | ||
|
|
||
| set(${content_var} "${${content_var}}" PARENT_SCOPE) | ||
| endfunction() | ||
|
|
||
| # Writes a .cpp file that includes Windows.h, then tests every provided Glaze header | ||
| function(write_header_test_source source_path use_win32_lean_and_mean) | ||
| set(source_content "") | ||
| append_windows_prelude(source_content ${use_win32_lean_and_mean}) | ||
|
|
||
| foreach(header_path IN LISTS ARGN) | ||
| string(REPLACE "\\" "/" header_include_path "${header_path}") | ||
| string(APPEND source_content | ||
| "#include <${header_include_path}>\n" | ||
| ) | ||
| append_windows_macro_asserts(source_content "was not preserved after including ${header_include_path}" FALSE TRUE) | ||
| endforeach() | ||
|
|
||
| file(WRITE "${source_path}" "${source_content}") | ||
| endfunction() | ||
|
|
||
| file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/generated") | ||
|
|
||
| set(source_path "${CMAKE_CURRENT_BINARY_DIR}/generated/${PROJECT_NAME}.cpp") | ||
| set(lean_source_path "${CMAKE_CURRENT_BINARY_DIR}/generated/glaze_windows_lean_header_compatibility.cpp") | ||
|
|
||
| # Generate regular source test file without WIN32_LEAN_AND_MEAN (simulating poor Windows environment) | ||
| write_header_test_source("${source_path}" FALSE Eigen/Dense ${glaze_windows_headers}) | ||
|
|
||
| # Generate the test source for Asio and Winsock2-dependent headers with #define WIN32_LEAN_AND_MEAN | ||
| write_header_test_source("${lean_source_path}" TRUE asio.hpp ${glaze_windows_lean_headers}) | ||
|
|
||
| add_library(${PROJECT_NAME} OBJECT "${source_path}" "${lean_source_path}") | ||
| target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_23) | ||
| target_compile_definitions(${PROJECT_NAME} PRIVATE GLZ_ENABLE_EETF ASIO_STANDALONE) | ||
| target_include_directories(${PROJECT_NAME} PRIVATE "${ERLANG_EI_INCLUDE_DIR}") | ||
|
|
||
| find_package(Eigen3 CONFIG REQUIRED) | ||
| find_package(asio CONFIG REQUIRED) | ||
| target_link_libraries(${PROJECT_NAME} PRIVATE Eigen3::Eigen asio::asio) | ||
|
|
||
| if(TARGET glaze::glaze) | ||
| target_link_libraries(${PROJECT_NAME} PRIVATE glaze::glaze) | ||
| elseif(TARGET glaze) | ||
| target_link_libraries(${PROJECT_NAME} PRIVATE glaze) | ||
| else() | ||
| target_include_directories(${PROJECT_NAME} PRIVATE "${GLAZE_SOURCE_DIR}/include") | ||
| endif() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.