diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e1e095..5a612de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,10 +9,10 @@ jobs: test: strategy: matrix: - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Install bison/flex (Linux) if: runner.os == 'Linux' @@ -22,11 +22,26 @@ jobs: if: runner.os == 'macOS' run: brew install bison flex + - name: Install bison/flex (Windows) + if: runner.os == 'Windows' + # windows-latest ships MSYS2 pre-installed at C:\msys64 (not on + # PATH). Its bison (3.8.2) satisfies parser.y's %require "3.8"; + # winflexbison3's Chocolatey package is stuck on bison 3.7.4 (2021 + # release) and fails that check. CMakeLists.txt's own WIN32 block + # points BISON_EXECUTABLE/FLEX_EXECUTABLE straight at the MSYS2 + # binaries. + run: C:\msys64\usr\bin\bash.exe -lc "pacman -Sy --noconfirm bison flex" + - name: Configure run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release + # --config/-C Release: a no-op on the single-config generators + # Linux/macOS use (CMAKE_BUILD_TYPE already picked the config), but + # required on Windows's default multi-config Visual Studio + # generator, which ignores CMAKE_BUILD_TYPE entirely -- passing it + # unconditionally on all three platforms is simpler than branching. - name: Build - run: cmake --build build -j + run: cmake --build build --config Release -j - name: Test - run: ctest --test-dir build --output-on-failure + run: ctest --test-dir build --output-on-failure -C Release diff --git a/CMakeLists.txt b/CMakeLists.txt index 065d748..5559131 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,18 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +# ponytail: MSVC always reports __cplusplus as 199711L regardless of the +# actual /std: level, for legacy compatibility -- a long-standing MSVC-only +# quirk, unless this flag opts into reporting the real value. Bison's own +# generated variant.hh (parser.tab.hpp) branches its whole emplace/copy/move +# API on `__cplusplus >= 201103L`; without this flag MSVC silently takes the +# pre-C++11 copy-based branch, which then fails to compile for any move-only +# %type (this grammar's NodePtr = unique_ptr) with a "deleted copy +# constructor" error that has nothing to do with our own code. +if(MSVC) + add_compile_options(/Zc:__cplusplus) +endif() + # ponytail: macOS ships an ancient (GPLv2, 2.3) bison; point CMake at a # homebrew bison (3.5+) if one is present, since our grammar needs # %skeleton "lalr1.cc" + api.value.type variant support. @@ -16,7 +28,34 @@ elseif(APPLE AND EXISTS "/usr/local/opt/bison/bin/bison") set(BISON_EXECUTABLE "/usr/local/opt/bison/bin/bison" CACHE FILEPATH "Bison executable" FORCE) endif() -find_package(BISON 3.5 REQUIRED) +# ponytail: Windows has no bison/flex at all by default. Prefer MSYS2's +# (kept current -- our grammar needs 3.8+, see parser.y's %require) over the +# winflexbison3 Chocolatey package, whose last release (2021) bundles bison +# 3.7.4 -- confirmed too old by an actual CI failure ("require bison 3.8, +# but have 3.7.4"), not assumed. MSYS2 ships pre-installed on GitHub's +# windows-latest runner image at C:/msys64 (just not on PATH); falls back to +# win_bison/win_flex (e.g. a local dev machine with winflexbison instead) if +# that path doesn't exist. +if(WIN32) + if(EXISTS "C:/msys64/usr/bin/bison.exe") + set(BISON_EXECUTABLE "C:/msys64/usr/bin/bison.exe" CACHE FILEPATH "Bison executable" FORCE) + else() + find_program(_win_bison_exe NAMES win_bison) + if(_win_bison_exe) + set(BISON_EXECUTABLE "${_win_bison_exe}" CACHE FILEPATH "Bison executable" FORCE) + endif() + endif() + if(EXISTS "C:/msys64/usr/bin/flex.exe") + set(FLEX_EXECUTABLE "C:/msys64/usr/bin/flex.exe" CACHE FILEPATH "Flex executable" FORCE) + else() + find_program(_win_flex_exe NAMES win_flex) + if(_win_flex_exe) + set(FLEX_EXECUTABLE "${_win_flex_exe}" CACHE FILEPATH "Flex executable" FORCE) + endif() + endif() +endif() + +find_package(BISON 3.8 REQUIRED) find_package(FLEX REQUIRED) include(FetchContent) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6ed85be..8a5e156 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -39,4 +39,6 @@ target_include_directories(openscad_cpp_parser PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ) target_link_libraries(openscad_cpp_parser PUBLIC nlohmann_json::nlohmann_json) -target_compile_options(openscad_cpp_parser PRIVATE -Wall -Wextra) +target_compile_options(openscad_cpp_parser PRIVATE + $<$>:-Wall;-Wextra> +) diff --git a/src/grammar/lexer.l b/src/grammar/lexer.l index c99f377..764c560 100644 --- a/src/grammar/lexer.l +++ b/src/grammar/lexer.l @@ -59,7 +59,10 @@ const std::unordered_map& keywordTable() { #define YY_USER_ACTION updateLocation(driver, yytext, yyleng); %} -%option noyywrap nounput noinput never-interactive +/* nounistd: MSVC/Windows has no ; flex only includes it for + * isatty()/POSIX I/O we never use (never-interactive already disables the + * interactive-terminal codepath), so this is a no-op everywhere else. */ +%option noyywrap nounput noinput never-interactive nounistd %x STR %x INCFILE diff --git a/src/grammar/parser.y b/src/grammar/parser.y index 52edae3..9f4cbcb 100644 --- a/src/grammar/parser.y +++ b/src/grammar/parser.y @@ -4,6 +4,13 @@ %define api.token.constructor %define api.value.type variant +// NodePtr (unique_ptr) is move-only. Every action already moves it +// explicitly (std::move($1) etc.), which is enough for GCC/Clang, but +// MSVC's more eager template instantiation of value_type::copy (used +// internally by emplace, referenced whether or not it's ever actually +// called at runtime) fails to compile for a non-copyable T without this -- +// the Bison manual's own documented fix for move-only semantic types. +%define api.value.automove %define parse.error verbose %locations diff --git a/tests/test_comments_and_files.cpp b/tests/test_comments_and_files.cpp index 8667729..43ff93d 100644 --- a/tests/test_comments_and_files.cpp +++ b/tests/test_comments_and_files.cpp @@ -57,7 +57,10 @@ class TempDir { }; void writeFile(const fs::path& p, const std::string& content) { - std::ofstream out(p); + // Binary mode: see the identical comment in test_source_map.cpp's own + // writeFile -- avoids Windows text-mode \r\n injection that the + // production reader (binary mode) would then faithfully preserve. + std::ofstream out(p, std::ios::binary); out << content; } diff --git a/tests/test_source_map.cpp b/tests/test_source_map.cpp index 524d296..2ecf1bd 100644 --- a/tests/test_source_map.cpp +++ b/tests/test_source_map.cpp @@ -52,7 +52,11 @@ class TempDir { }; void writeFile(const fs::path& p, const std::string& content) { - std::ofstream out(p); + // Binary mode: the production code (source_map.cpp) reads included + // files back in binary mode too, so a text-mode write here would + // silently inject \r\n on Windows that the reader (correctly) never + // strips, corrupting every fixture's expected content on that platform. + std::ofstream out(p, std::ios::binary); out << content; }