From 7e1b8a765d174a83ba33ade9d891f5bbd7f4c0be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Fr=C3=B6jdh?= Date: Mon, 5 Oct 2026 19:13:45 +0200 Subject: [PATCH] Dev/win compile (#402) Collection of fixes to make aare build on Windows. Requires the previous PRs for compilation to succeed. - Replaced `or` keyword with || - updated cmake file - fix for 32 bit long in fseek closes #395 closes #396 --- CMakeLists.txt | 54 +++++++++++++++++++------------ include/aare/FilePtr.hpp | 20 +++++++++++- include/aare/NumpyFile.hpp | 3 +- python/src/bind_ClusterVector.hpp | 12 +++---- python/src/np_helper.hpp | 9 ++++-- src/ClusterVector.test.cpp | 6 ++-- src/FilePtr.cpp | 2 +- src/NumpyFile.cpp | 12 +++---- src/ROI.cpp | 4 +-- src/RawFile.cpp | 2 +- src/fit/Fit.cpp | 4 ++- src/fit/Fit.test.cpp | 2 +- 12 files changed, 85 insertions(+), 45 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b4a501a8..453414a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -254,18 +254,30 @@ target_compile_features(aare_compiler_flags INTERFACE cxx_std_17) # MSVC specific # # ############################################################################## if(MSVC) - add_compile_definitions(AARE_MSVC) - if(CMAKE_BUILD_TYPE STREQUAL "Release") - message(STATUS "Release build") - target_compile_options(aare_compiler_flags INTERFACE /O2) - else() - message(STATUS "Debug build") - target_compile_options(aare_compiler_flags - INTERFACE /Od /Zi /MDd /D_ITERATOR_DEBUG_LEVEL=2) - target_link_options(aare_compiler_flags INTERFACE /DEBUG:FULL) - endif() - target_compile_options(aare_compiler_flags INTERFACE /w # disable warnings + # CMake sets the per-configuration optimisation, debug-info and + # runtime-library flags itself, so only warnings and conformance here. + target_compile_options( + aare_compiler_flags + INTERFACE /W4 # -Wall -Wextra + /permissive- # standard conformance, like CMAKE_CXX_EXTENSIONS OFF + /utf-8 # sources are UTF-8 + /we4715 # not all control paths return a value, like + # -Werror=return-type + /wd4244 # narrowing conversions: -Wconversion is not enabled + /wd4267 # on gcc/clang either + /wd4068 # unknown pragma: the GCC diagnostic pragmas + /wd4324 # structure padded due to alignas: intended ) + target_compile_definitions(aare_compiler_flags + INTERFACE _CRT_SECURE_NO_WARNINGS) + + if(AARE_WARNINGS_AS_ERRORS) + target_compile_options(aare_compiler_flags INTERFACE /WX) + endif() + + if(AARE_ASAN) + message(WARNING "AARE_ASAN is not supported with MSVC. Ignoring.") + endif() else() # ############################################################################ @@ -318,22 +330,22 @@ else() endif() endif() + if(AARE_ASAN) + message(STATUS "AddressSanitizer enabled") + target_compile_options( + aare_compiler_flags INTERFACE -fsanitize=address,undefined,pointer-compare + -fno-omit-frame-pointer) + target_link_libraries( + aare_compiler_flags INTERFACE -fsanitize=address,undefined,pointer-compare + -fno-omit-frame-pointer) + endif() + endif() # GCC/Clang specific if(AARE_PYTHON_BINDINGS) add_subdirectory(python) endif() -if(AARE_ASAN) - message(STATUS "AddressSanitizer enabled") - target_compile_options( - aare_compiler_flags INTERFACE -fsanitize=address,undefined,pointer-compare - -fno-omit-frame-pointer) - target_link_libraries( - aare_compiler_flags INTERFACE -fsanitize=address,undefined,pointer-compare - -fno-omit-frame-pointer) -endif() - if(AARE_TESTS) enable_testing() add_subdirectory(tests) diff --git a/include/aare/FilePtr.hpp b/include/aare/FilePtr.hpp index d9753006..16ea198d 100644 --- a/include/aare/FilePtr.hpp +++ b/include/aare/FilePtr.hpp @@ -1,11 +1,29 @@ // SPDX-License-Identifier: MPL-2.0 #pragma once #include "aare/defs.hpp" +#include #include #include namespace aare { +// MSVC's long is 32 bits, so fseek/ftell stop working past 2 GB there +inline int fseek64(FILE *fp, int64_t offset, int whence) { +#ifdef _WIN32 + return _fseeki64(fp, offset, whence); +#else + return fseek(fp, static_cast(offset), whence); +#endif +} + +inline int64_t ftell64(FILE *fp) { +#ifdef _WIN32 + return _ftelli64(fp); +#else + return ftell(fp); +#endif +} + /** * \brief RAII wrapper for FILE pointer */ @@ -23,7 +41,7 @@ class FilePtr { FILE *get(); ssize_t tell(); void seek(ssize_t offset, int whence = SEEK_SET) { - if (fseek(fp_, offset, whence) != 0) + if (fseek64(fp_, offset, whence) != 0) throw std::runtime_error("Error seeking in file"); } std::string error_msg(); diff --git a/include/aare/NumpyFile.hpp b/include/aare/NumpyFile.hpp index caa68531..0d7a523d 100644 --- a/include/aare/NumpyFile.hpp +++ b/include/aare/NumpyFile.hpp @@ -2,6 +2,7 @@ #pragma once #include "aare/Dtype.hpp" #include "aare/FileInterface.hpp" +#include "aare/FilePtr.hpp" #include "aare/NumpyHelpers.hpp" #include "aare/defs.hpp" @@ -77,7 +78,7 @@ class NumpyFile : public FileInterface { */ template NDArray load() { NDArray arr(make_shape(m_header.shape)); - if (fseek(fp, static_cast(header_size), SEEK_SET)) { + if (fseek64(fp, header_size, SEEK_SET)) { throw std::runtime_error(LOCATION + "Error seeking to the start of the data"); } diff --git a/python/src/bind_ClusterVector.hpp b/python/src/bind_ClusterVector.hpp index 9b031e5d..c7b2932a 100644 --- a/python/src/bind_ClusterVector.hpp +++ b/python/src/bind_ClusterVector.hpp @@ -124,7 +124,7 @@ void define_ClusterVector(py::module &m, const std::string &typestr) { .def_property_readonly( "fmt", [typestr](ClusterVector &self) { - return fmt_format; + return fmt_format(); }, "PEP 3118 format string for one stored cluster.") @@ -145,11 +145,11 @@ void define_ClusterVector(py::module &m, const std::string &typestr) { .def_buffer( [typestr](ClusterVector &self) -> py::buffer_info { return py::buffer_info( - self.data(), /* Pointer to buffer */ - self.item_size(), /* Size of one scalar */ - fmt_format, /* Format descriptor */ - 1, /* Number of dimensions */ - {self.size()}, /* Buffer dimensions */ + self.data(), /* Pointer to buffer */ + self.item_size(), /* Size of one scalar */ + fmt_format(), /* Format descriptor */ + 1, /* Number of dimensions */ + {self.size()}, /* Buffer dimensions */ {self.item_size()} /* Strides (in bytes) for each index */ ); }); diff --git a/python/src/np_helper.hpp b/python/src/np_helper.hpp index d21aceff..83880470 100644 --- a/python/src/np_helper.hpp +++ b/python/src/np_helper.hpp @@ -112,8 +112,13 @@ struct fmt_format_trait> { } }; -template -auto fmt_format = fmt_format_trait::value(); +// Function-local static rather than a variable template: MSVC does not run +// the dynamic initializer of a variable template instantiated from inside +// another template, which left the format string empty. +template const std::string &fmt_format() { + static const std::string value = fmt_format_trait::value(); + return value; +} /** * Helper function to allocate image data given item size and shape diff --git a/src/ClusterVector.test.cpp b/src/ClusterVector.test.cpp index 7d5b3910..088f0059 100644 --- a/src/ClusterVector.test.cpp +++ b/src/ClusterVector.test.cpp @@ -181,7 +181,8 @@ TEST_CASE("Summing 3x1 clusters of int64") { Cluster c3 = {11, 12, {13, 14, 15}}; cv.push_back(c3); - REQUIRE(cv.capacity() == 4); + // growth factor is implementation defined (2x libstdc++, 1.5x MSVC) + REQUIRE(cv.capacity() >= 3); REQUIRE(cv.size() == 3); /* @@ -237,7 +238,8 @@ TEST_CASE("Push back more than initial capacity") { Cluster c3 = {11, 12, {13, 14, 15, 16}}; cv.push_back(c3); REQUIRE(cv.size() == 3); - REQUIRE(cv.capacity() == 4); + // growth factor is implementation defined (2x libstdc++, 1.5x MSVC) + REQUIRE(cv.capacity() >= 3); Cluster *ptr = reinterpret_cast *>(cv.data()); diff --git a/src/FilePtr.cpp b/src/FilePtr.cpp index f0e0844c..c6edfe62 100644 --- a/src/FilePtr.cpp +++ b/src/FilePtr.cpp @@ -27,7 +27,7 @@ FilePtr::operator bool() const noexcept { return fp_ != nullptr; } FILE *FilePtr::get() { return fp_; } ssize_t FilePtr::tell() { - auto pos = ftell(fp_); + auto pos = ftell64(fp_); if (pos == -1) throw std::runtime_error( fmt::format("Error getting file position: {}", error_msg())); diff --git a/src/NumpyFile.cpp b/src/NumpyFile.cpp index d70ae5bb..ad9cdf6f 100644 --- a/src/NumpyFile.cpp +++ b/src/NumpyFile.cpp @@ -45,7 +45,7 @@ void NumpyFile::write_impl(void *data, uint64_t size) { if (!(m_mode == "w" || m_mode == "a")) { throw std::invalid_argument("File not open for writing"); } - if (fseek(fp, 0, SEEK_END)) + if (fseek64(fp, 0, SEEK_END)) throw std::runtime_error("Could not seek to end of file"); size_t const rc = fwrite(data, size, 1, fp); if (rc != 1) { @@ -67,8 +67,8 @@ void NumpyFile::get_frame_into(size_t frame_number, std::byte *image_buf) { if (frame_number > m_header.shape[0]) { throw std::invalid_argument("Frame number out of range"); } - if (fseek(fp, header_size + frame_number * m_bytes_per_frame, - SEEK_SET)) // NOLINT + if (fseek64(fp, header_size + frame_number * m_bytes_per_frame, + SEEK_SET)) // NOLINT throw std::runtime_error("Could not seek to frame"); size_t const rc = fread(image_buf, m_bytes_per_frame, 1, fp); @@ -100,15 +100,15 @@ void NumpyFile::read_into(std::byte *image_buf, size_t n_frames) { NumpyFile::~NumpyFile() noexcept { if (m_mode == "w" || m_mode == "a") { // determine number of frames - if (fseek(fp, 0, SEEK_END)) { + if (fseek64(fp, 0, SEEK_END)) { std::cout << "Could not seek to end of file" << std::endl; } - size_t const file_size = ftell(fp); + size_t const file_size = ftell64(fp); size_t const data_size = file_size - initial_header_len; size_t const n_frames = data_size / m_bytes_per_frame; // update number of frames in header (first element of shape) m_header.shape[0] = n_frames; - if (fseek(fp, 0, SEEK_SET)) { + if (fseek64(fp, 0, SEEK_SET)) { std::cout << "Could not seek to beginning of file" << std::endl; } // create string stream to contain header diff --git a/src/ROI.cpp b/src/ROI.cpp index 7e91c49d..56882015 100644 --- a/src/ROI.cpp +++ b/src/ROI.cpp @@ -20,7 +20,7 @@ bool complete_ROI(const ROI &roi, const DetectorGeometry &geometry) { bool complete_ROI(const std::vector &rois, const DetectorGeometry &geometry) { - if (rois.empty() or rois.size() > 1) { + if (rois.empty() || rois.size() > 1) { return false; } else { return complete_ROI(rois[0], geometry); @@ -34,7 +34,7 @@ bool complete_ROI(const ROIGeometry &roi, const DetectorGeometry &geometry) { bool complete_ROI(const std::vector &rois, const DetectorGeometry &geometry) { - if (rois.empty() or rois.size() > 1) { + if (rois.empty() || rois.size() > 1) { return false; } else { return complete_ROI(rois[0], geometry); diff --git a/src/RawFile.cpp b/src/RawFile.cpp index 1e33bf83..e416a27e 100644 --- a/src/RawFile.cpp +++ b/src/RawFile.cpp @@ -302,7 +302,7 @@ void RawFile::open_subfiles(const size_t roi_index) { DetectorHeader RawFile::read_header(const std::filesystem::path &fname) { DetectorHeader h{}; - FILE *fp = fopen(fname.string().c_str(), "r"); + FILE *fp = fopen(fname.string().c_str(), "rb"); if (!fp) throw std::runtime_error(fmt::format( "Could not open file '{}' for frame index 0", fname.string())); diff --git a/src/fit/Fit.cpp b/src/fit/Fit.cpp index 8b34c177..05dfa5d8 100644 --- a/src/fit/Fit.cpp +++ b/src/fit/Fit.cpp @@ -268,7 +268,9 @@ void fit_3d(const FitModel &model, NDView x, NDView y, NDView y_err, NDView par_out, NDView err_out, NDView chi2_out, int n_threads) { - constexpr std::size_t npar = Model::npar; + // static so the lambda below can use it as an array size without + // capturing it, which MSVC otherwise rejects + static constexpr std::size_t npar = Model::npar; detail::check_fit_3d_shapes(x, y, y_err, par_out, err_out, chi2_out); const bool has_errors = (y_err.size() > 0); diff --git a/src/fit/Fit.test.cpp b/src/fit/Fit.test.cpp index 9c1c7311..1edadce6 100644 --- a/src/fit/Fit.test.cpp +++ b/src/fit/Fit.test.cpp @@ -1100,7 +1100,7 @@ TEST_CASE("Samples with a zero error are ignored even when NaN", "[fit]") { INFO("minimizer " << name(minimizer)); const double nan = std::numeric_limits::quiet_NaN(); - constexpr ssize_t n = 10; + static constexpr ssize_t n = 10; // static: used inside GENERATE's lambda NDArray x({n}); NDArray y({n}); NDArray y_err({n}, 1.0);