diff --git a/smoke/CMakeLists.txt b/smoke/CMakeLists.txt index 50833bc..144f111 100644 --- a/smoke/CMakeLists.txt +++ b/smoke/CMakeLists.txt @@ -12,6 +12,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) include(FetchContent) +include(CheckCXXSourceCompiles) set(PROTOCYTE_REPO_ROOT "${CMAKE_CURRENT_LIST_DIR}/..") set(PROTOCYTE_SMOKE_PROTO_DIR "${CMAKE_CURRENT_LIST_DIR}/proto") @@ -42,6 +43,29 @@ set(PROTOCYTE_GENERATED_FILES "${PROTOCYTE_GENERATED_DIR}/protocyte/runtime/runtime.hpp" ) +check_cxx_source_compiles([=[ +#include +#if !defined(__cpp_lib_format) || __cpp_lib_format < 201907L +#error "std::format is unavailable" +#endif +#include +#include +#include + +int main() { + using Formatter = ::std::formatter<::std::string_view, char>; + static_assert(::std::is_default_constructible_v); + const auto formatted = ::std::format("{}", ::std::string_view {"ok"}); + return formatted == "ok" ? 0 : 1; +} +]=] PROTOCYTE_SMOKE_HAS_STD_FORMAT) + +function(protocyte_smoke_enable_std_format_if_available target_name) + if(PROTOCYTE_SMOKE_HAS_STD_FORMAT) + target_compile_definitions("${target_name}" PRIVATE PROTOCYTE_ENABLE_STD_FORMAT=1) + endif() +endfunction() + if(PROTOCYTE_SMOKE_REGENERATE OR PROTOCYTE_SMOKE_BUILD_BENCHMARKS) set(PROTOCYTE_FETCH_PROTOBUF "${PROTOCYTE_SMOKE_FETCH_PROTOBUF}") else() @@ -212,6 +236,7 @@ target_compile_definitions( PROTOCYTE_ENABLE_HOSTED_ALLOCATOR=1 PROTOCYTE_ENABLE_STD_STRING_VIEW=1 ) +protocyte_smoke_enable_std_format_if_available(protocyte_host_smoke) target_compile_features(protocyte_host_smoke PRIVATE cxx_std_20) target_link_libraries(protocyte_host_smoke PRIVATE Catch2::Catch2WithMain) @@ -239,6 +264,7 @@ if(PROTOCYTE_SMOKE_BUILD_BENCHMARKS) PROTOCYTE_ENABLE_HOSTED_ALLOCATOR=1 PROTOCYTE_ENABLE_STD_STRING_VIEW=1 ) + protocyte_smoke_enable_std_format_if_available(protocyte_host_benchmark) target_compile_features(protocyte_host_benchmark PRIVATE cxx_std_20) target_link_libraries(protocyte_host_benchmark PRIVATE benchmark::benchmark_main) @@ -268,6 +294,7 @@ if(PROTOCYTE_SMOKE_BUILD_BENCHMARKS) PROTOCYTE_ENABLE_HOSTED_ALLOCATOR=1 PROTOCYTE_ENABLE_STD_STRING_VIEW=1 ) + protocyte_smoke_enable_std_format_if_available(protocyte_host_protobuf_benchmark) target_compile_features(protocyte_host_protobuf_benchmark PRIVATE cxx_std_20) target_link_libraries( protocyte_host_protobuf_benchmark diff --git a/smoke/generated/protocyte/runtime/runtime.hpp b/smoke/generated/protocyte/runtime/runtime.hpp index 187b041..78c9af0 100644 --- a/smoke/generated/protocyte/runtime/runtime.hpp +++ b/smoke/generated/protocyte/runtime/runtime.hpp @@ -11,7 +11,14 @@ #include #include -#if PROTOCYTE_ENABLE_STD_STRING_VIEW +#if PROTOCYTE_ENABLE_STD_FORMAT +#include +#if defined(__cpp_lib_format) && __cpp_lib_format >= 201907L +#include +#endif +#endif + +#if PROTOCYTE_ENABLE_STD_STRING_VIEW || PROTOCYTE_ENABLE_STD_FORMAT || PROTOCYTE_ENABLE_FMT_FORMAT #include #endif @@ -2974,6 +2981,12 @@ namespace protocyte { typename Config::Bytes bytes_; }; +#if PROTOCYTE_ENABLE_FMT_FORMAT + template std::string_view format_as(const String &value) noexcept { + return ::std::string_view {value.data(), value.size()}; + } +#endif + template struct Box { using Context = typename Config::Context; @@ -4299,4 +4312,18 @@ namespace protocyte { } // namespace protocyte +#if PROTOCYTE_ENABLE_STD_FORMAT && defined(__cpp_lib_format) && __cpp_lib_format >= 201907L +namespace std { + + template struct formatter<::protocyte::String, char> + : public ::std::formatter<::std::string_view, char> { + template auto format(const ::protocyte::String &value, FormatContext &ctx) const { + return ::std::formatter<::std::string_view, char>::format(::std::string_view {value.data(), value.size()}, + ctx); + } + }; + +} // namespace std +#endif + #endif // PROTOCYTE_RUNTIME_RUNTIME_HPP diff --git a/smoke/src/host_smoke.cpp b/smoke/src/host_smoke.cpp index 7fc0746..eb70444 100644 --- a/smoke/src/host_smoke.cpp +++ b/smoke/src/host_smoke.cpp @@ -2621,6 +2621,10 @@ TEST_CASE("byte setters accept contiguous byte containers", "[smoke][runtime][by CHECK(converted_span == std::string_view {"hello"}); #if defined(__cpp_lib_format) CHECK(std::format("{}", message.f_string()) == "hello"); +#if PROTOCYTE_ENABLE_STD_FORMAT + CHECK(std::format("{}", message.mutable_f_string()) == "hello"); + CHECK(std::format("{:>8}", message.mutable_f_string()) == " hello"); +#endif #endif const std::string_view converted_string = message.mutable_f_string(); CHECK(converted_string == converted_span); @@ -2635,6 +2639,10 @@ TEST_CASE("byte setters accept contiguous byte containers", "[smoke][runtime][by REQUIRE(embedded_nul_string_view); CHECK(message.f_string().size() == 3u); CHECK(view_equal(message.f_string(), *embedded_nul_string_view)); +#if defined(__cpp_lib_format) && PROTOCYTE_ENABLE_STD_FORMAT + const auto formatted_embedded_nul = std::format("{}", message.mutable_f_string()); + CHECK(formatted_embedded_nul == std::string {"a\0b", 3u}); +#endif const char *c_string_payload = "hello from pointer"; require_success(message.set_f_string(c_string_payload)); diff --git a/src/protocyte/runtime/runtime.hpp b/src/protocyte/runtime/runtime.hpp index 187b041..78c9af0 100644 --- a/src/protocyte/runtime/runtime.hpp +++ b/src/protocyte/runtime/runtime.hpp @@ -11,7 +11,14 @@ #include #include -#if PROTOCYTE_ENABLE_STD_STRING_VIEW +#if PROTOCYTE_ENABLE_STD_FORMAT +#include +#if defined(__cpp_lib_format) && __cpp_lib_format >= 201907L +#include +#endif +#endif + +#if PROTOCYTE_ENABLE_STD_STRING_VIEW || PROTOCYTE_ENABLE_STD_FORMAT || PROTOCYTE_ENABLE_FMT_FORMAT #include #endif @@ -2974,6 +2981,12 @@ namespace protocyte { typename Config::Bytes bytes_; }; +#if PROTOCYTE_ENABLE_FMT_FORMAT + template std::string_view format_as(const String &value) noexcept { + return ::std::string_view {value.data(), value.size()}; + } +#endif + template struct Box { using Context = typename Config::Context; @@ -4299,4 +4312,18 @@ namespace protocyte { } // namespace protocyte +#if PROTOCYTE_ENABLE_STD_FORMAT && defined(__cpp_lib_format) && __cpp_lib_format >= 201907L +namespace std { + + template struct formatter<::protocyte::String, char> + : public ::std::formatter<::std::string_view, char> { + template auto format(const ::protocyte::String &value, FormatContext &ctx) const { + return ::std::formatter<::std::string_view, char>::format(::std::string_view {value.data(), value.size()}, + ctx); + } + }; + +} // namespace std +#endif + #endif // PROTOCYTE_RUNTIME_RUNTIME_HPP diff --git a/tests/test_cmake.py b/tests/test_cmake.py index 55d0aa5..e6e4004 100644 --- a/tests/test_cmake.py +++ b/tests/test_cmake.py @@ -131,6 +131,25 @@ def test_cmake_requires_python_314_for_codegen_wrapper() -> None: assert "find_package(Python3 3.14 COMPONENTS Interpreter REQUIRED)" in functions +def test_smoke_cmake_gates_std_format_opt_in_on_compile_probe() -> None: + smoke_cmake = (Path(__file__).resolve().parents[1] / "smoke" / "CMakeLists.txt").read_text(encoding="utf-8") + + assert "include(CheckCXXSourceCompiles)" in smoke_cmake + assert "check_cxx_source_compiles(" in smoke_cmake + assert "#include " in smoke_cmake + assert "#if !defined(__cpp_lib_format) || __cpp_lib_format < 201907L" in smoke_cmake + assert '#error "std::format is unavailable"' in smoke_cmake + assert "#include " in smoke_cmake + assert "#include " in smoke_cmake + assert "::std::formatter<::std::string_view, char>" in smoke_cmake + assert "::std::format(\"{}\", ::std::string_view {\"ok\"})" in smoke_cmake + assert "PROTOCYTE_SMOKE_HAS_STD_FORMAT" in smoke_cmake + assert "if(PROTOCYTE_SMOKE_HAS_STD_FORMAT)" in smoke_cmake + assert "target_compile_definitions(\"${target_name}\" PRIVATE PROTOCYTE_ENABLE_STD_FORMAT=1)" in smoke_cmake + assert "\n PROTOCYTE_ENABLE_STD_FORMAT=1" not in smoke_cmake + assert "\n PROTOCYTE_ENABLE_STD_FORMAT=1" not in smoke_cmake + + def test_prerelease_cmake_version_file_marks_versioned_requests_unsuitable() -> None: template = ( Path(__file__).resolve().parents[1] / "cmake" / "protocyteConfigVersionPrerelease.cmake.in" diff --git a/tests/test_plugin.py b/tests/test_plugin.py index a55075b..9c21645 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -183,8 +183,11 @@ def test_runtime_byte_containers_use_bulk_copy_helpers() -> None: )[0] assert "#include " in runtime_header - assert "#if PROTOCYTE_ENABLE_STD_STRING_VIEW\n#include \n#endif" in runtime_header + assert "#if PROTOCYTE_ENABLE_STD_FORMAT\n#include \n#if defined(__cpp_lib_format) && __cpp_lib_format >= 201907L\n#include \n#endif\n#endif" in runtime_header + assert "#if PROTOCYTE_ENABLE_STD_STRING_VIEW || PROTOCYTE_ENABLE_STD_FORMAT || PROTOCYTE_ENABLE_FMT_FORMAT\n#include \n#endif" in runtime_header assert "#ifdef PROTOCYTE_ENABLE_STD_STRING_VIEW" not in runtime_header + assert "#ifdef PROTOCYTE_ENABLE_STD_FORMAT" not in runtime_header + assert "#ifdef PROTOCYTE_ENABLE_FMT_FORMAT" not in runtime_header assert "inline void copy_bytes(u8 *dst, const u8 *src, const usize count) noexcept" in runtime_header assert "if (!count || dst == src)" in runtime_header assert "::std::memmove(dst, src, count);" in runtime_header @@ -218,6 +221,12 @@ def test_runtime_byte_containers_use_bulk_copy_helpers() -> None: assert "const char *data() const noexcept" in string_body assert "usize length() const noexcept { return size(); }" in string_body assert "operator ::std::string_view() const noexcept { return view(); }" in string_body + assert "#if PROTOCYTE_ENABLE_FMT_FORMAT\n template std::string_view format_as(const String &value) noexcept" in runtime_header + assert "return ::std::string_view {value.data(), value.size()};" in runtime_header + assert "#if PROTOCYTE_ENABLE_STD_FORMAT && defined(__cpp_lib_format) && __cpp_lib_format >= 201907L\nnamespace std {" in runtime_header + assert "template struct formatter<::protocyte::String, char>" in runtime_header + assert "public ::std::formatter<::std::string_view, char>" in runtime_header + assert "auto format(const ::protocyte::String &value, FormatContext &ctx) const" in runtime_header assert "Status assign(const Span view) noexcept" in string_body assert "Status assign(const Span view) noexcept" in string_body assert "copy_bytes(out, data_ + pos_, count);" in slice_reader_body