diff --git a/CMakeLists.txt b/CMakeLists.txt index d37aae8a4..1b4bbde03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,6 +206,8 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/python/__version__.py.in install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR}) +find_package(fmt 9 REQUIRED) + #--- project specific subdirectories ------------------------------------------- add_subdirectory(src) @@ -222,8 +224,6 @@ if(BUILD_TESTING) add_subdirectory(tests) endif() -find_package(fmt 9 REQUIRED) - add_subdirectory(tools) add_subdirectory(python) diff --git a/cmake/podioConfig.cmake.in b/cmake/podioConfig.cmake.in index 674602faa..b2d20c9e4 100644 --- a/cmake/podioConfig.cmake.in +++ b/cmake/podioConfig.cmake.in @@ -30,6 +30,7 @@ if(NOT "@REQUIRE_PYTHON_VERSION@" STREQUAL "") else() find_dependency(Python3 COMPONENTS Interpreter Development) endif() +find_dependency(fmt @fmt_VERSION@) SET(PODIO_ENABLE_SIO @ENABLE_SIO@) if(PODIO_ENABLE_SIO) diff --git a/include/podio/ObjectID.h b/include/podio/ObjectID.h index 02efc48df..0dbf0e7cd 100644 --- a/include/podio/ObjectID.h +++ b/include/podio/ObjectID.h @@ -1,6 +1,12 @@ #ifndef PODIO_OBJECTID_H #define PODIO_OBJECTID_H +// Hide the fmt dependency from cling, so that ROOT does not need to be able to +// find the fmt headers when it parses the podio headers at runtime +#if !defined(__CLING__) + #include +#endif + #include #include #include @@ -66,4 +72,9 @@ struct std::hash { } }; +#if !defined(__CLING__) +template <> +struct fmt::formatter : fmt::ostream_formatter {}; +#endif + #endif diff --git a/include/podio/UserDataCollection.h b/include/podio/UserDataCollection.h index 361bb0034..d74aaa20f 100644 --- a/include/podio/UserDataCollection.h +++ b/include/podio/UserDataCollection.h @@ -8,6 +8,12 @@ #include "podio/detail/Pythonizations.h" #include "podio/utilities/TypeHelpers.h" +// Hide the fmt dependency from cling, so that ROOT does not need to be able to +// find the fmt headers when it parses the podio headers at runtime +#if !defined(__CLING__) + #include +#endif + #define PODIO_ADD_USER_TYPE(type) \ template <> \ consteval const char* userDataTypeName() { \ @@ -354,4 +360,9 @@ constexpr std::string_view UserDataCollection::dataTypeName; } // namespace podio +#if !defined(__CLING__) +template +struct fmt::formatter> : fmt::ostream_formatter {}; +#endif + #endif diff --git a/include/podio/detail/Link.h b/include/podio/detail/Link.h index 9193f80c1..98666dffd 100644 --- a/include/podio/detail/Link.h +++ b/include/podio/detail/Link.h @@ -12,6 +12,12 @@ #include "nlohmann/json.hpp" #endif +// Hide the fmt dependency from cling, so that ROOT does not need to be able to +// find the fmt headers when it parses the podio headers at runtime +#if !defined(__CLING__) + #include +#endif + #include #include #include @@ -348,8 +354,8 @@ class LinkT { podio::utils::MaybeSharedPtr m_obj{nullptr}; }; -template -std::ostream& operator<<(std::ostream& os, const Link& link) { +template +std::ostream& operator<<(std::ostream& os, const LinkT& link) { if (!link.isAvailable()) { return os << "[not available]"; } @@ -382,4 +388,9 @@ struct std::hash> { } }; +#if !defined(__CLING__) +template +struct fmt::formatter> : fmt::ostream_formatter {}; +#endif + #endif // PODIO_DETAIL_LINK_H diff --git a/include/podio/detail/LinkCollectionImpl.h b/include/podio/detail/LinkCollectionImpl.h index cf498c31d..ee2564118 100644 --- a/include/podio/detail/LinkCollectionImpl.h +++ b/include/podio/detail/LinkCollectionImpl.h @@ -28,6 +28,12 @@ #include "nlohmann/json.hpp" #endif +// Hide the fmt dependency from cling, so that ROOT does not need to be able to +// find the fmt headers when it parses the podio headers at runtime +#if !defined(__CLING__) + #include +#endif + #include #include #include @@ -463,4 +469,9 @@ void to_json(nlohmann::json& j, const podio::LinkCollection& collect } // namespace podio +#if !defined(__CLING__) +template +struct fmt::formatter> : fmt::ostream_formatter {}; +#endif + #endif // PODIO_DETAIL_LINKCOLLECTIONIMPL_H diff --git a/python/templates/Collection.h.jinja2 b/python/templates/Collection.h.jinja2 index c74a50d5c..453f18584 100644 --- a/python/templates/Collection.h.jinja2 +++ b/python/templates/Collection.h.jinja2 @@ -30,6 +30,12 @@ #include #include +// Hide the fmt dependency from cling, so that ROOT does not need to be able to +// find the fmt headers when it parses the podio headers at runtime +#if !defined(__CLING__) +#include +#endif + namespace podio { struct RelationNames; } @@ -280,6 +286,11 @@ void to_json(nlohmann::json& j, const {{ class.bare_type }}Collection& collectio {{ utils.namespace_close(class.namespace) }} +#if !defined(__CLING__) +template <> +struct fmt::formatter<{% if class.namespace %}{{ class.namespace }}::{% endif %}{{ class.bare_type }}Collection> : fmt::ostream_formatter {}; +#endif + {{ workarounds.ld_library_path(class, "Collection", ["valueTypeName", "dataTypeName"]) }} #endif diff --git a/python/templates/Component.h.jinja2 b/python/templates/Component.h.jinja2 index 19a9e7b8b..ad93170d9 100644 --- a/python/templates/Component.h.jinja2 +++ b/python/templates/Component.h.jinja2 @@ -11,6 +11,12 @@ {% if generate_current_version %} #include +// Hide the fmt dependency from cling, so that ROOT does not need to be able to +// find the fmt headers when it parses the podio headers at runtime +#if !defined(__CLING__) +#include +#endif + #if defined(PODIO_JSON_OUTPUT) && !defined(__CLING__) #include "nlohmann/json_fwd.hpp" #endif @@ -58,4 +64,11 @@ public: {{ utils.namespace_close(class.namespace) }} +{% if generate_current_version %} +#if !defined(__CLING__) +template <> +struct fmt::formatter<{{ class.full_type }}> : fmt::ostream_formatter {}; +#endif +{% endif %} + #endif diff --git a/python/templates/Interface.h.jinja2 b/python/templates/Interface.h.jinja2 index b23fb1408..c5b13b014 100644 --- a/python/templates/Interface.h.jinja2 +++ b/python/templates/Interface.h.jinja2 @@ -14,6 +14,12 @@ #include "podio/utilities/TypeHelpers.h" #include "podio/detail/OrderKey.h" +// Hide the fmt dependency from cling, so that ROOT does not need to be able to +// find the fmt headers when it parses the podio headers at runtime +#if !defined(__CLING__) +#include +#endif + #include #include #include @@ -190,4 +196,9 @@ struct std::hash<{{ class.full_type }}> { } }; +#if !defined(__CLING__) +template <> +struct fmt::formatter<{{ class.full_type }}> : fmt::ostream_formatter {}; +#endif + #endif diff --git a/python/templates/MutableObject.h.jinja2 b/python/templates/MutableObject.h.jinja2 index ecb9ca5af..87b61dbfb 100644 --- a/python/templates/MutableObject.h.jinja2 +++ b/python/templates/MutableObject.h.jinja2 @@ -62,4 +62,7 @@ private: {{ macros.std_hash(class, prefix='Mutable') }} +{{ macros.ostream_formatter(class, prefix='Mutable') }} + + #endif diff --git a/python/templates/Object.h.jinja2 b/python/templates/Object.h.jinja2 index 9c77afd4d..c2d51ccd6 100644 --- a/python/templates/Object.h.jinja2 +++ b/python/templates/Object.h.jinja2 @@ -15,6 +15,12 @@ #include "podio/utilities/MaybeSharedPtr.h" #include "podio/detail/OrderKey.h" +// Hide the fmt dependency from cling, so that ROOT does not need to be able to +// find the fmt headers when it parses the podio headers at runtime +#if !defined(__CLING__) +#include +#endif + #include #include @@ -79,6 +85,8 @@ std::ostream& operator<<(std::ostream& o, const {{ class.bare_type }}& value); {{ macros.std_hash(class) }} +{{ macros.ostream_formatter(class) }} + {{ workarounds.ld_library_path(class) }} #endif diff --git a/python/templates/macros/declarations.jinja2 b/python/templates/macros/declarations.jinja2 index 75058f5bc..5f9df0fd5 100644 --- a/python/templates/macros/declarations.jinja2 +++ b/python/templates/macros/declarations.jinja2 @@ -153,3 +153,11 @@ struct std::hash<{{ namespace }}{{ prefix }}{{ class.bare_type }}> { } }; {% endmacro %} + +{% macro ostream_formatter(class, prefix='') %} +{% set namespace = class.namespace + '::' if class.namespace else '' %} +#if !defined(__CLING__) +template <> +struct fmt::formatter<{{ namespace }}{{ prefix }}{{ class.bare_type }}> : fmt::ostream_formatter {}; +#endif +{% endmacro %} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3a52679c4..966c623be 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -79,6 +79,7 @@ SET(core_headers PODIO_ADD_LIB_AND_DICT(podio "${core_headers}" "${core_sources}" selection.xml) target_compile_options(podio PRIVATE -pthread) target_link_libraries(podio PRIVATE Python3::Python) +target_link_libraries(podio PUBLIC fmt::fmt) # For Frame.h if (ROOT_VERSION VERSION_LESS 6.36) target_compile_definitions(podio PUBLIC PODIO_ROOT_OLDER_6_36=1) diff --git a/tests/unittests/interface_types.cpp b/tests/unittests/interface_types.cpp index 235456b1e..c756ddff7 100644 --- a/tests/unittests/interface_types.cpp +++ b/tests/unittests/interface_types.cpp @@ -219,3 +219,14 @@ TEST_CASE("InterfaceType extension model", "[interface-types][extension]") { REQUIRE(wrapper.isA()); REQUIRE(wrapper.as().energy() == 4.2f); } + +TEST_CASE("InterfaceType formatting", "[interface-types][basics][formatting]") { + auto iface = iextension::EnergyInterface::makeEmpty(); + auto formatted = fmt::format("{}", iface); + REQUIRE(formatted == "[not available]"); + + iface = ExampleCluster{}; + formatted = fmt::format("{}", iface); + REQUIRE_FALSE(formatted.empty()); + REQUIRE(formatted != "[not available]"); +} diff --git a/tests/unittests/links.cpp b/tests/unittests/links.cpp index fd76eaed6..ee7f9680b 100644 --- a/tests/unittests/links.cpp +++ b/tests/unittests/links.cpp @@ -15,6 +15,8 @@ #include "nlohmann/json.hpp" #endif +#include + #include #include #include @@ -305,6 +307,22 @@ TEST_CASE("Links templated accessors", "[links]") { } } // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) + +TEST_CASE("Link formatting", "[links]") { + TestL link; + auto formatted = fmt::format("{}", link); + REQUIRE_FALSE(formatted.empty()); + REQUIRE(formatted != "[not available]"); + + auto emptyLink = TestL::makeEmpty(); + auto emptyFmt = fmt::format("{}", emptyLink); + REQUIRE(emptyFmt == "[not available]"); + + TestMutL mutLink; + formatted = fmt::format("{}", mutLink); + REQUIRE(formatted != "[not avialable]"); +} + TEST_CASE("LinkCollection collection concept", "[links][concepts]") { STATIC_REQUIRE(podio::CollectionType); STATIC_REQUIRE(std::is_same_v, TestL>); @@ -449,6 +467,19 @@ TEST_CASE("LinkCollection basics", "[links]") { } } +TEST_CASE("LinkCollection formatting", "[links][formatting]") { + podio::LinkCollection links; + + auto formatted = fmt::format("{}", links); + REQUIRE_FALSE(formatted.empty()); + + links.create(); + links.create(); + + auto formatted2 = fmt::format("{}", links); + REQUIRE(formatted2.size() > formatted.size()); +} + auto createLinkCollections(const size_t nElements = 3u) { auto colls = std::make_tuple(TestLColl(), ExampleHitCollection(), ExampleClusterCollection()); diff --git a/tests/unittests/unittest.cpp b/tests/unittests/unittest.cpp index 92fea37f3..35099bf60 100644 --- a/tests/unittests/unittest.cpp +++ b/tests/unittests/unittest.cpp @@ -20,6 +20,7 @@ // podio specific includes #include "podio/Frame.h" #include "podio/GenericParameters.h" +#include "podio/ObjectID.h" #include "podio/ROOTLegacyReader.h" #include "podio/ROOTReader.h" #include "podio/ROOTWriter.h" @@ -60,12 +61,26 @@ #include "datamodel/MutableExampleWithArray.h" #include "datamodel/MutableExampleWithComponent.h" #include "datamodel/MutableExampleWithExternalExtraCode.h" +#include "datamodel/NamespaceInNamespaceStruct.h" #include "datamodel/StructWithExtraCode.h" #include "datamodel/datamodel.h" #include "extension_model/extension_model.h" #include "podio/UserDataCollection.h" +#include + +TEST_CASE("ObjectID formatting", "[basics][formatting]") { + auto objId = podio::ObjectID{}; + auto formatted = fmt::format("{}", objId); + REQUIRE(formatted == "ffffffff|-1"); + + objId.collectionID = 42; + objId.index = 123; + formatted = fmt::format("{}", objId); + REQUIRE(formatted == fmt::format("{:8x}|123", 42)); +} + TEST_CASE("AutoDelete", "[basics][memory-management]") { auto coll = EventInfoCollection(); auto hit1 = MutableEventInfo(); @@ -138,6 +153,30 @@ TEST_CASE("makeEmpty", "[basics]") { REQUIRE(hit.energy() == 0); } +TEST_CASE("Object formatting", "[basics][formatting]") { + ExampleCluster cluster; + auto formatted = fmt::format("{}", cluster); + REQUIRE_FALSE(formatted.empty()); + REQUIRE(formatted != "[not avaialble]"); + + cluster = ExampleCluster::makeEmpty(); + formatted = fmt::format("{}", cluster); + REQUIRE(formatted == "[not available]"); + + auto mutCluster = MutableExampleCluster{}; + formatted = fmt::format("{}", mutCluster); + REQUIRE_FALSE(formatted.empty()); + REQUIRE(formatted != "[not available]"); + + auto typeWithComponent = ExampleWithArrayComponent{}; + formatted = fmt::format("{}", typeWithComponent); + REQUIRE_FALSE(formatted.empty()); + + auto nspComp = ex2::NamespaceInNamespaceStruct{}; + formatted = fmt::format("{}", nspComp); + REQUIRE_FALSE(formatted.empty()); +} + TEST_CASE("Cyclic dependencies", "[LEAK-FAIL][basics][relations][memory-management]") { SECTION("with collections") { auto coll1 = ExampleForCyclicDependency1Collection(); @@ -418,6 +457,9 @@ TEST_CASE("UserDataCollection basics", "[basics]") { coll.print(sstr); REQUIRE(sstr.str() == "[1, 2, 3]"); + + auto formatted = fmt::format("{}", coll); + REQUIRE_FALSE(formatted.empty()); } SECTION("access") { @@ -647,6 +689,19 @@ TEST_CASE("Equality", "[basics]") { REQUIRE(clu != cluster); } +TEST_CASE("Collection formatting", "[basics]") { + ExampleClusterCollection clusters; + auto cluster = clusters.create(); + cluster.energy(42.5f); + auto formatted = fmt::format("{}", clusters); + REQUIRE_FALSE(formatted.empty()); + + ExampleWithComponentCollection components; + auto comp = components.create(); + formatted = fmt::format("{}", components); + REQUIRE_FALSE(formatted.empty()); +} + TEST_CASE("UserInitialization", "[basics][code-gen]") { ExampleWithUserInitCollection coll; // Default initialization values should work even through the create factory