Skip to content

Commit acd75a1

Browse files
committed
fix: patch avro-cpp headers at configure time for fmt v12 compatibility on Windows
Signed-off-by: Krzysztof Milde <Krzysztof.Milde@Point72.com>
1 parent e9d65cb commit acd75a1

2 files changed

Lines changed: 103 additions & 51 deletions

File tree

cpp/cmake/modules/FindAvro.cmake

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,107 @@
11
find_path(Avro_INCLUDE_DIR NAMES avro/Encoder.hh)
22
find_library(Avro_LIBRARY NAMES avrocpp libavrocpp)
33

4+
# =============================================================================
5+
# Workaround for conda-forge avro-cpp fmt::formatter incompatibility on Windows
6+
# =============================================================================
7+
# conda-forge's avro-cpp has fmt::formatter specializations with non-const
8+
# format() methods, but fmt v12+ requires const. This causes MSVC error C2766.
9+
#
10+
# Solution: On Windows, we check if the avro headers have this bug, and if so,
11+
# we create patched versions in the build directory and prepend them to the
12+
# include path so they shadow the broken originals.
13+
#
14+
# This workaround can be removed once conda-forge updates avro-cpp.
15+
# =============================================================================
16+
17+
set(CSP_AVRO_PATCHED_INCLUDE_DIR "")
18+
19+
if(WIN32 AND Avro_INCLUDE_DIR AND NOT CSP_USE_VCPKG)
20+
# Check if avro/Node.hh has the non-const format() bug
21+
# The buggy pattern is: "auto format(...) {" without "const" before the brace
22+
set(_avro_node_hh "${Avro_INCLUDE_DIR}/avro/Node.hh")
23+
set(_avro_types_hh "${Avro_INCLUDE_DIR}/avro/Types.hh")
24+
set(_needs_patching FALSE)
25+
26+
if(EXISTS "${_avro_node_hh}")
27+
file(READ "${_avro_node_hh}" _node_hh_content)
28+
29+
# Check if the file contains fmt::formatter and non-const format()
30+
# We look for "auto format" followed by ")" then whitespace then "{"
31+
# without "const" in between
32+
string(FIND "${_node_hh_content}" "fmt::formatter<avro::Name>" _has_formatter)
33+
if(NOT _has_formatter EQUAL -1)
34+
# Check specifically for the non-const pattern
35+
# Buggy: auto format(const avro::Name &n, FormatContext &ctx) {
36+
# Fixed: auto format(const avro::Name &n, FormatContext &ctx) const {
37+
string(REGEX MATCH "auto format\\(const avro::Name[^)]+\\)[^c]*\\{" _buggy_pattern "${_node_hh_content}")
38+
if(_buggy_pattern)
39+
set(_needs_patching TRUE)
40+
endif()
41+
endif()
42+
endif()
43+
44+
if(_needs_patching)
45+
message(STATUS "Detected avro-cpp with non-const fmt::formatter bug - applying build-time patch")
46+
47+
# Create patched headers directory structure
48+
set(CSP_AVRO_PATCHED_INCLUDE_DIR "${CMAKE_BINARY_DIR}/_patched_avro_headers")
49+
set(_patched_avro_dir "${CSP_AVRO_PATCHED_INCLUDE_DIR}/avro")
50+
file(MAKE_DIRECTORY "${_patched_avro_dir}")
51+
52+
# Patch Node.hh
53+
# Replace: auto format(const avro::Name &n, FormatContext &ctx) {
54+
# With: auto format(const avro::Name &n, FormatContext &ctx) const {
55+
string(REGEX REPLACE
56+
"(auto format\\(const avro::Name[^)]+\\))[ \t\r\n]*(\\{)"
57+
"\\1 const \\2"
58+
_patched_node_content
59+
"${_node_hh_content}"
60+
)
61+
file(WRITE "${_patched_avro_dir}/Node.hh" "${_patched_node_content}")
62+
message(STATUS " Patched: avro/Node.hh -> ${_patched_avro_dir}/Node.hh")
63+
64+
# Patch Types.hh if it exists and has the same bug
65+
if(EXISTS "${_avro_types_hh}")
66+
file(READ "${_avro_types_hh}" _types_hh_content)
67+
string(FIND "${_types_hh_content}" "fmt::formatter<avro::Type>" _has_type_formatter)
68+
if(NOT _has_type_formatter EQUAL -1)
69+
string(REGEX MATCH "auto format\\(avro::Type[^)]+\\)[^c]*\\{" _types_buggy "${_types_hh_content}")
70+
if(_types_buggy)
71+
string(REGEX REPLACE
72+
"(auto format\\(avro::Type[^)]+\\))[ \t\r\n]*(\\{)"
73+
"\\1 const \\2"
74+
_patched_types_content
75+
"${_types_hh_content}"
76+
)
77+
file(WRITE "${_patched_avro_dir}/Types.hh" "${_patched_types_content}")
78+
message(STATUS " Patched: avro/Types.hh -> ${_patched_avro_dir}/Types.hh")
79+
endif()
80+
endif()
81+
endif()
82+
endif()
83+
endif()
84+
485
if (NOT TARGET Avro::avrocpp)
586
add_library(Avro::avrocpp SHARED IMPORTED)
6-
set_property(TARGET Avro::avrocpp PROPERTY
7-
IMPORTED_LOCATION "${Avro_LIBRARY}")
8-
target_include_directories(Avro::avrocpp INTERFACE ${Avro_INCLUDE_DIR})
87+
88+
# On Windows, IMPORTED_IMPLIB is the .lib file, IMPORTED_LOCATION is the .dll
89+
# On other platforms, IMPORTED_LOCATION is the shared library
90+
if(WIN32)
91+
set_property(TARGET Avro::avrocpp PROPERTY IMPORTED_IMPLIB "${Avro_LIBRARY}")
92+
else()
93+
set_property(TARGET Avro::avrocpp PROPERTY IMPORTED_LOCATION "${Avro_LIBRARY}")
94+
endif()
95+
96+
# If we have patched headers, prepend them to include path so they shadow originals
97+
if(CSP_AVRO_PATCHED_INCLUDE_DIR)
98+
target_include_directories(Avro::avrocpp INTERFACE
99+
${CSP_AVRO_PATCHED_INCLUDE_DIR} # Patched headers first (shadows originals)
100+
${Avro_INCLUDE_DIR} # Original headers for everything else
101+
)
102+
else()
103+
target_include_directories(Avro::avrocpp INTERFACE ${Avro_INCLUDE_DIR})
104+
endif()
9105
endif()
10106

11107
include(FindPackageHandleStandardArgs)
Lines changed: 4 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,10 @@
11
#ifndef _IN_CSP_ADAPTERS_UTILS_AVROINCLUDES_H
22
#define _IN_CSP_ADAPTERS_UTILS_AVROINCLUDES_H
33

4-
// =============================================================================
5-
// Workaround for avro-cpp fmt::formatter incompatibility on Windows
6-
// =============================================================================
7-
// conda-forge's avro-cpp (as of early 2025) has outdated fmt::formatter
8-
// specializations with non-const format() methods, but fmt v11+ requires const.
9-
//
10-
// The avro formatters are guarded by:
11-
// #if FMT_VERSION >= 90000
12-
//
13-
// We temporarily set FMT_VERSION below this threshold to prevent avro from
14-
// defining its broken formatters, then restore it and define const-correct
15-
// versions.
16-
//
17-
// This workaround can be removed once conda-forge updates avro-cpp.
18-
// =============================================================================
4+
// Centralized avro includes.
5+
// On Windows conda-forge builds, headers are patched at CMake configure time
6+
// (see FindAvro.cmake) to fix fmt v12 compatibility.
197

20-
#include <fmt/format.h>
21-
22-
#ifdef _MSC_VER
23-
#pragma push_macro("FMT_VERSION")
24-
#undef FMT_VERSION
25-
#define FMT_VERSION 80000
26-
#endif
27-
28-
// Include all avro headers that may be needed
298
#include <avro/Compiler.hh>
309
#include <avro/Decoder.hh>
3110
#include <avro/Encoder.hh>
@@ -36,27 +15,4 @@
3615
#include <avro/Stream.hh>
3716
#include <avro/ValidSchema.hh>
3817

39-
#ifdef _MSC_VER
40-
#undef FMT_VERSION
41-
#pragma pop_macro("FMT_VERSION")
42-
43-
// Define const-correct fmt::formatter specializations for avro types
44-
// These mirror the definitions in avro's headers but with the required const
45-
46-
template<>
47-
struct fmt::formatter<avro::Name> : fmt::formatter<std::string> {
48-
auto format(const avro::Name &n, fmt::format_context &ctx) const {
49-
return fmt::format_to(ctx.out(), "{}", n.fullname());
50-
}
51-
};
52-
53-
template<>
54-
struct fmt::formatter<avro::Type> : fmt::formatter<std::string> {
55-
auto format(avro::Type t, fmt::format_context &ctx) const {
56-
return fmt::format_to(ctx.out(), "{}", avro::toString(t));
57-
}
58-
};
59-
60-
#endif // _MSC_VER
61-
62-
#endif // _IN_CSP_ADAPTERS_UTILS_AVROINCLUDES_H
18+
#endif

0 commit comments

Comments
 (0)