diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94e22e0..cf4e080 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,6 +75,60 @@ jobs: retention-days: 1 if-no-files-found: error + # ----------------------------------------------------------------------- + # build-windows: the same build under MSVC. + # + # Everything else in this file is ubuntu-only, so nothing here has ever + # compiled with MSVC, and portability breaks land silently: __attribute__, + # naming an automatic constexpr from a captureless lambda, and POSIX-only + # calls all build clean on gcc and stop MSVC on the first file. + # + # CPU only, no model downloads — this guards the compile, not the numbers, + # which the ubuntu smoke-test job already covers. GGML_NATIVE=ON matches the + # ubuntu build job: CI only has to run on its own runner. + # ----------------------------------------------------------------------- + build-windows: + name: Build & unit tests (MSVC) + runs-on: windows-2022 + timeout-minutes: 30 + steps: + - name: Checkout (with submodules) + uses: actions/checkout@v4 + with: + submodules: recursive + + # The CMake configure-time hook shells out to bash, which is not on PATH + # in the MSVC environment; run it explicitly first. + - name: Apply ggml patches (Git Bash) + shell: bash + run: bash scripts/apply_ggml_patches.sh + + - name: MSVC environment + uses: ilammy/msvc-dev-cmd@v1 + + - name: Configure CMake + shell: bash + run: | + cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DRFDETR_BUILD_TESTS=ON \ + -DRFDETR_BUILD_CLI=ON \ + -DGGML_NATIVE=ON + + - name: Build + shell: bash + run: cmake --build build -j + + - name: Run unit tests + shell: bash + run: ctest --test-dir build --output-on-failure + + - name: Usage banner + shell: bash + run: | + out=$(./build/bin/rfdetr-cli.exe 2>&1 || true) + grep -qi usage <<<"$out" + # ----------------------------------------------------------------------- # smoke-test: download nano (all 4 quants) + base-f16 from HF, run detect # on a committed test image, compare JSON output against committed refs. diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index 4253f78..abf9dde 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -11,7 +11,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -131,13 +133,15 @@ static int cmd_detect(const rfdetr_cli::DetectArgs& a) { /* 7. Optional per-detection mask PNGs (seg models only). */ if (!a.masks_dir.empty()) { - /* Create the masks directory if it doesn't exist. */ - struct stat st_buf; - if (::stat(a.masks_dir.c_str(), &st_buf) != 0) { - if (::mkdir(a.masks_dir.c_str(), 0755) != 0) { - std::fprintf(stderr, "failed to create masks dir '%s'\n", - a.masks_dir.c_str()); - } + /* Create the masks directory if it doesn't exist. std::filesystem + * rather than stat + ::mkdir: MSVC has no POSIX mkdir (only _mkdir, + * in ), and create_directories already succeeds silently + * when the directory is there, so the stat probe goes with it. */ + std::error_code mkdir_ec; + std::filesystem::create_directories(a.masks_dir, mkdir_ec); + if (mkdir_ec) { + std::fprintf(stderr, "failed to create masks dir '%s'\n", + a.masks_dir.c_str()); } size_t n_written = 0; for (size_t i = 0; i < n; ++i) { diff --git a/src/common.hpp b/src/common.hpp index dc1a5a5..c8718e6 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -9,8 +9,19 @@ * in tests without needing to include this header. */ void rfdetr_internal_log(rfdetr_log_level lvl, const char* msg); +/* Format-string checking where the compiler offers it. MSVC does not + * understand __attribute__, and MinGW's printf is the gnu_printf dialect — + * the same three cases ggml.h handles with GGML_ATTRIBUTE_FORMAT. */ +#ifndef __GNUC__ +# define RFDETR_ATTRIBUTE_FORMAT(...) +#elif defined(__MINGW32__) && !defined(__clang__) +# define RFDETR_ATTRIBUTE_FORMAT(...) __attribute__((format(gnu_printf, __VA_ARGS__))) +#else +# define RFDETR_ATTRIBUTE_FORMAT(...) __attribute__((format(printf, __VA_ARGS__))) +#endif + /* printf-style wrapper. Builds the string then dispatches. */ void rfdetr_logf(rfdetr_log_level lvl, const char* fmt, ...) - __attribute__((format(printf, 2, 3))); + RFDETR_ATTRIBUTE_FORMAT(2, 3); #endif diff --git a/src/model_loader.cpp b/src/model_loader.cpp index 0326fcf..5c237d8 100644 --- a/src/model_loader.cpp +++ b/src/model_loader.cpp @@ -116,7 +116,10 @@ bool get_bool(gguf_context* g, const char* key, bool& out) { * with A = -0.5 for antialias=True. */ void bicubic_resample_patch_grid(const float* src, int src_side, int dim, float* dst, int dst_side) { - constexpr float A = -0.5f; // antialias=True path uses Keys, not Catmull-Rom + /* `static` so the captureless lambda below can name it: MSVC rejects an + * automatic constexpr there (C3493) where gcc/clang accept it. Static + * storage duration needs no capture, and is portable. */ + static constexpr float A = -0.5f; // antialias=True path uses Keys, not Catmull-Rom auto kernel = [](float x) -> float { const float ax = std::fabs(x); if (ax < 1.0f) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 05a5bd7..8dc07c3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -38,7 +38,10 @@ rfdetr_add_test(test_visualize) rfdetr_add_test(test_cli_smoke) -if(TARGET rfdetr-cli) +# test_cli_integration drives the CLI as a child process through , +# so it is POSIX-only. Everything else in this directory is portable; excluding +# just this one keeps the rest of the suite running under MSVC. +if(TARGET rfdetr-cli AND NOT WIN32) rfdetr_add_test(test_cli_integration) target_compile_definitions(test_cli_integration PRIVATE RFDETR_CLI_BINARY="$")