From 1abde09edf2b9ad7bc36b67f2bdf83e90c1d193c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20B=C3=B6rschig?= Date: Mon, 29 Jun 2026 17:26:02 +0200 Subject: [PATCH 1/7] config: factor yaml parsing out into a separate silkit_yaml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marius Börschig --- CMakeLists.txt | 1 + SilKit/cmake/SilKitConfig.cmake.in | 6 + SilKit/include/CMakeLists.txt | 9 +- .../include/silkit_yaml/BasicYamlReader.hpp | 274 ++++++++++++++++++ .../include/silkit_yaml/BasicYamlWriter.hpp | 132 +++++++++ SilKit/include/silkit_yaml/README.md | 94 ++++++ SilKit/include/silkit_yaml/RapidyamlImpl.cpp | 19 ++ .../include/silkit_yaml/YamlParserUtils.hpp | 73 +++++ SilKit/include/silkit_yaml/YamlSerdes.hpp | 90 ++++++ SilKit/source/config/CMakeLists.txt | 66 ++++- SilKit/source/config/CapabilitiesParser.cpp | 38 +++ ...ParserUtils.hpp => CapabilitiesParser.hpp} | 8 - SilKit/source/config/YamlParser.hpp | 55 +--- SilKit/source/config/YamlParserUtils.cpp | 93 ------ SilKit/source/config/YamlReader.hpp | 248 +--------------- SilKit/source/config/YamlWriter.hpp | 111 +------ .../source/core/vasio/VAsioCapabilities.cpp | 2 +- 17 files changed, 811 insertions(+), 508 deletions(-) create mode 100644 SilKit/include/silkit_yaml/BasicYamlReader.hpp create mode 100644 SilKit/include/silkit_yaml/BasicYamlWriter.hpp create mode 100644 SilKit/include/silkit_yaml/README.md create mode 100644 SilKit/include/silkit_yaml/RapidyamlImpl.cpp create mode 100644 SilKit/include/silkit_yaml/YamlParserUtils.hpp create mode 100644 SilKit/include/silkit_yaml/YamlSerdes.hpp create mode 100644 SilKit/source/config/CapabilitiesParser.cpp rename SilKit/source/config/{YamlParserUtils.hpp => CapabilitiesParser.hpp} (60%) delete mode 100644 SilKit/source/config/YamlParserUtils.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 997fc2fb2..e7613f174 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ option(SILKIT_USE_SYSTEM_LIBRARIES "Use the libraries installed on the system fo option(SILKIT_BUILD_REPRODUCIBLE "Creates a reproducible build by ommiting timestamps/unique build ids" ON) option(SILKIT_BUILD_LINUX_PACKAGE "Creates SIL Kit builds suitable for package managers in Linux Distributions (.deb)" OFF) option(SILKIT_BUILD_LTO "Build with link time optimizations" OFF) +option(SILKIT_INSTALL_YAML "Install the SilKit::Yaml header-only auxiliary YAML library (silkit_yaml; unstable API/ABI)" OFF) if(SILKIT_BUILD_LINUX_PACKAGE) diff --git a/SilKit/cmake/SilKitConfig.cmake.in b/SilKit/cmake/SilKitConfig.cmake.in index e1de372e9..7d4282652 100644 --- a/SilKit/cmake/SilKitConfig.cmake.in +++ b/SilKit/cmake/SilKitConfig.cmake.in @@ -11,3 +11,9 @@ find_dependency(Threads) get_filename_component(SILKIT_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) include("${SILKIT_CMAKE_DIR}/SilKitTargets.cmake") + +# Optional unstable auxiliary YAML library (present only if the package was built +# with -DSILKIT_INSTALL_YAML=ON). Exposes the SilKit::Yaml target. +if(EXISTS "${SILKIT_CMAKE_DIR}/SilKitYamlTargets.cmake") + include("${SILKIT_CMAKE_DIR}/SilKitYamlTargets.cmake") +endif() diff --git a/SilKit/include/CMakeLists.txt b/SilKit/include/CMakeLists.txt index 55957a820..16571e9fd 100644 --- a/SilKit/include/CMakeLists.txt +++ b/SilKit/include/CMakeLists.txt @@ -30,7 +30,9 @@ install( COMPONENT dev INCLUDES DESTINATION ${INSTALL_INCLUDE_DIR} # Destination for header files ) -# Copy all headers from the source directory to the proper destination +# Copy all headers from the source directory to the proper destination. +# The silkit_yaml/ folder is the unstable auxiliary YAML library; it is installed +# separately and only when -DSILKIT_INSTALL_YAML=ON (see source/config/CMakeLists.txt). install( DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/ DESTINATION ${INSTALL_INCLUDE_DIR} @@ -38,11 +40,14 @@ install( FILES_MATCHING PATTERN *.hpp PATTERN *.ipp + PATTERN "silkit_yaml" EXCLUDE ) # for C API install( DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/ DESTINATION ${INSTALL_INCLUDE_DIR} COMPONENT dev - FILES_MATCHING PATTERN *.h + FILES_MATCHING + PATTERN *.h + PATTERN "silkit_yaml" EXCLUDE ) diff --git a/SilKit/include/silkit_yaml/BasicYamlReader.hpp b/SilKit/include/silkit_yaml/BasicYamlReader.hpp new file mode 100644 index 000000000..64a392287 --- /dev/null +++ b/SilKit/include/silkit_yaml/BasicYamlReader.hpp @@ -0,0 +1,274 @@ +// SPDX-FileCopyrightText: 2025 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#pragma once + +// UNSTABLE API: this auxiliary library does NOT carry the API/ABI stability +// guarantees of the main SIL Kit API (the silkit/ headers). Pin your SIL Kit +// version. See silkit_yaml/README for details. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "silkit/participant/exception.hpp" + +#include "rapidyaml.hpp" + +#include "silkit_yaml/YamlParserUtils.hpp" + +namespace VSilKit { + +template +class BasicYamlReader +{ +protected: + ryml::Parser& _parser; + ryml::ConstNodeRef _node; + +public: + BasicYamlReader(ryml::Parser& parser_, ryml::ConstNodeRef node_) + : _parser(parser_) + , _node(node_) + { + } + +public: + template , bool> = true> + void OptionalRead(T& val, const std::string& name) + { + auto&& child = GetChildSafe(name); + if (child.IsValid()) + { + child.Read(val); + } + } + + void OptionalRead(bool& val, const std::string& name) + { + auto&& child = GetChildSafe(name); + if (child.IsValid()) + { + child.Read(val); + } + } + + template + void OptionalRead(std::optional& val, const std::string& name) + { + auto&& child = GetChildSafe(name); + if (child.IsValid()) + { + T tmp{}; + child.Read(tmp); + val = std::move(tmp); // needs a proper setter to set "has_value" + } + } + + + template , bool> = true> + void OptionalRead(T& val, const std::string& name) + { + auto tmp = ryml::fmt::overflow_checked(val); + (void)_node.get_if(ryml::to_csubstr(name), &tmp); + } + + template + void OptionalRead_deprecated_alternative(ConfigT& value, const std::string& fieldName, + std::initializer_list deprecatedFieldNames) + { + if (!(IsMap() || IsSequence())) + { + return; + } + + auto hasChild = [this](auto&& name) { + auto&& c = GetChildSafe(name); + return c.IsValid(); + }; + + std::vector presentDeprecatedFieldNames; + std::copy_if(deprecatedFieldNames.begin(), deprecatedFieldNames.end(), + std::back_inserter(presentDeprecatedFieldNames), hasChild); + + if (HasKey(fieldName) && presentDeprecatedFieldNames.size() >= 1) + { + std::stringstream ss; + ss << "The key \"" << fieldName << "\" and the deprecated alternatives"; + for (const auto& deprecatedFieldName : presentDeprecatedFieldNames) + { + ss << " \"" << deprecatedFieldName << "\""; + } + ss << " are present."; + throw SilKit::ConfigurationError{ss.str()}; + } + + if (presentDeprecatedFieldNames.size() >= 2) + { + std::stringstream ss; + ss << "The deprecated keys"; + for (const auto& deprecatedFieldName : presentDeprecatedFieldNames) + { + ss << " \"" << deprecatedFieldName << "\""; + } + ss << " are present."; + throw SilKit::ConfigurationError{ss.str()}; + } + + OptionalRead(value, fieldName); + for (const auto& deprecatedFieldName : deprecatedFieldNames) + { + OptionalRead(value, deprecatedFieldName); + } + } + +public: + template + void ReadKeyValue(T& value, const std::string& name) + { + auto&& child = GetChildSafe(name); + + if (!child.IsValid()) + { + std::ostringstream s; + s << "missing key: " << name; + throw MakeConfigurationError(s.str()); + } + + child.Read(value); + } + +public: + template + void Read(T& value) + { + _node >> value; + } + + template + void Read(std::chrono::duration& obj) + { + Rep value{}; + auto tmp = ryml::fmt::overflow_checked(value); + Read(tmp); + obj = std::chrono::milliseconds{value}; + } + + template + void Read(std::vector& val) + { + if (!IsSequence()) + { + throw MakeConfigurationError("Expected a sequence."); + } + + for (auto&& i : _node.cchildren()) + { + T element{}; + MakeImpl(i).Read(element); + val.emplace_back(std::move(element)); + } + } + +protected: + bool IsValid() const + { + return !_node.invalid(); + } + + bool IsMap() const + { + return _node.is_map(); + } + + bool IsScalar() const + { + return _node.is_val() || _node.is_keyval(); + } + + bool IsSequence() const + { + return _node.is_seq(); + } + + bool IsExistingString(const char* str) const + { + if (!_node.is_val()) + { + return false; + } + return _node.val() == ryml::to_csubstr(str); + } + + bool IsEmpty() const + { + return _node.empty(); + } + + bool IsString(const char* string) const + { + return IsScalar() && (_node.val() == ryml::to_csubstr(string)); + } + + bool HasKey(const std::string& name) const + { + return HasKey(_node, name); + } + + static bool HasKey(ryml::ConstNodeRef node, const std::string& name) + { + return node.is_map() && !node.find_child(ryml::to_csubstr(name)).invalid(); + } + + auto MakeConfigurationError(const std::string_view message) const -> SilKit::ConfigurationError + { + const auto location = _parser.location(_node); + return VSilKit::MakeConfigurationError(location, message); + } + + auto GetChildSafe(const std::string& name) const -> Impl + { + if (HasKey(name)) + { + return MakeImpl(_node.find_child(ryml::to_csubstr(name))); + } + + if (IsSequence()) + { + for (const auto& child : _node.cchildren()) + { + if (child.is_container() && HasKey(child, name)) + { + return MakeImpl(child.find_child(ryml::to_csubstr(name))); + } + } + } + + return MakeImpl({}); + } + + auto MakeImpl(ryml::ConstNodeRef node_) const -> Impl + { + return Impl{_parser, node_}; + } + +private: + auto AsImpl() -> Impl& + { + return static_cast(*this); + } + + auto AsImpl() const -> const Impl& + { + return static_cast(*this); + } +}; + +} // namespace VSilKit diff --git a/SilKit/include/silkit_yaml/BasicYamlWriter.hpp b/SilKit/include/silkit_yaml/BasicYamlWriter.hpp new file mode 100644 index 000000000..91f3bf84f --- /dev/null +++ b/SilKit/include/silkit_yaml/BasicYamlWriter.hpp @@ -0,0 +1,132 @@ +// SPDX-FileCopyrightText: 2025 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#pragma once + +// UNSTABLE API: this auxiliary library does NOT carry the API/ABI stability +// guarantees of the main SIL Kit API (the silkit/ headers). Pin your SIL Kit +// version. See silkit_yaml/README for details. + +#include +#include +#include +#include +#include + +#include "silkit/participant/exception.hpp" + +#include "rapidyaml.hpp" + +namespace VSilKit { + +template +struct BasicYamlWriter +{ + ryml::NodeRef node; + +public: + BasicYamlWriter(ryml::NodeRef node_) + : node(node_) + { + } + +public: + template + void OptionalWrite(const std::optional& val, const std::string& name) + { + if (val.has_value()) + { + WriteKeyValue(name, val.value()); + } + } + + template + void OptionalWrite(const std::vector& val, const std::string& name) + { + if (!val.empty()) + { + WriteKeyValue(name, val); + } + } + + void OptionalWrite(const std::string& val, const std::string& name) + { + if (!val.empty()) + { + WriteKeyValue(name, val); + } + } + + template + void NonDefaultWrite(const T& val, const std::string& name, const T& defaultValue) + { + if (!(val == defaultValue)) + { + WriteKeyValue(name, val); + } + } + + template + void WriteKeyValue(const std::string& name, const T& val) + { + if (!node.is_map()) + { + throw SilKit::ConfigurationError("Parse error: trying to access child of something not a map"); + } + + auto writer = MakeImpl(node.append_child() << ryml::key(name)); + writer.Write(val); + } + + template + void Write(const T& val) + { + node << val; + } + + template + void Write(const std::vector& val) + { + node |= ryml::SEQ; + for (auto&& el : val) + { + auto writer = MakeImpl(node.append_child()); + writer.Write(el); + } + } + +protected: + void MakeMap() + { + node |= ryml::MAP; + } + + auto MakeConfigurationError(const char* message) const -> SilKit::ConfigurationError + { + std::ostringstream s; + + s << "error writing configuration: " << message; + + return SilKit::ConfigurationError{s.str()}; + } + +protected: + auto MakeImpl(ryml::NodeRef node_) const -> Impl + { + return Impl{node_}; + } + +private: + auto AsImpl() -> Impl& + { + return static_cast(*this); + } + + auto AsImpl() const -> const Impl& + { + return static_cast(*this); + } +}; + +} // namespace VSilKit diff --git a/SilKit/include/silkit_yaml/README.md b/SilKit/include/silkit_yaml/README.md new file mode 100644 index 000000000..d1eba6812 --- /dev/null +++ b/SilKit/include/silkit_yaml/README.md @@ -0,0 +1,94 @@ + + +# SilKit::Yaml — auxiliary YAML parsing for out-of-tree projects + +This is a **header-only auxiliary library** that exposes the generic YAML +machinery used internally by SIL Kit's configuration parser, so that projects +living outside the SIL Kit tree can parse **their own** YAML formats against +**their own** schemata. It is a thin wrapper around +[rapidyaml](https://github.com/biojppm/rapidyaml). + +## Stability + +> **UNSTABLE API/ABI.** Unlike the main SIL Kit API (the `silkit/` headers), +> this library does **not** carry any source- or binary-compatibility guarantee +> across SIL Kit versions. The API lives in the internal `VSilKit` namespace and +> may change at any time. **Pin the exact SIL Kit version** you build against. + +The bundled `rapidyaml.hpp` is a specific vendored snapshot; the templates are +written against exactly that version. Parsing errors are reported by throwing +`SilKit::ConfigurationError` (from `silkit/participant/exception.hpp`). + +## What's here + +| Header | Purpose | +|---|---| +| `BasicYamlReader.hpp` | `VSilKit::BasicYamlReader` CRTP base for deserialization | +| `BasicYamlWriter.hpp` | `VSilKit::BasicYamlWriter` CRTP base for serialization | +| `YamlSerdes.hpp` | `VSilKit::Deserialize` / `Serialize` / `SerializeAsJson` | +| `YamlParserUtils.hpp` | rapidyaml error callbacks + `MakeConfigurationError` | +| `rapidyaml.hpp` | the bundled rapidyaml single header | +| `RapidyamlImpl.cpp` | the one TU that compiles the rapidyaml implementation | + +## Usage + +Build SIL Kit with `-DSILKIT_INSTALL_YAML=ON` so these files are installed and +the `SilKit::Yaml` CMake target is exported. + +```cmake +find_package(SilKit REQUIRED) +add_executable(my_tool + main.cpp + ${SILKIT_YAML_INCLUDE_DIR}/silkit_yaml/RapidyamlImpl.cpp # compile ryml impl ONCE +) +target_link_libraries(my_tool PRIVATE SilKit::Yaml) +``` + +`SilKit::Yaml` carries the include paths and the required +`RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS=1` definition. `RapidyamlImpl.cpp` must be +compiled in **exactly one** target — omit it if you already compile rapidyaml +elsewhere. + +```cpp +#include "silkit_yaml/BasicYamlReader.hpp" +#include "silkit_yaml/BasicYamlWriter.hpp" +#include "silkit_yaml/YamlSerdes.hpp" + +struct MyConfig +{ + std::string name; + int count{}; +}; + +struct MyReader : VSilKit::BasicYamlReader +{ + using BasicYamlReader::BasicYamlReader; + using BasicYamlReader::Read; + + void Read(MyConfig& c) + { + ReadKeyValue(c.name, "Name"); + OptionalRead(c.count, "Count"); + } +}; + +struct MyWriter : VSilKit::BasicYamlWriter +{ + using BasicYamlWriter::BasicYamlWriter; + using BasicYamlWriter::Write; + + void Write(const MyConfig& c) + { + MakeMap(); + WriteKeyValue("Name", c.name); + WriteKeyValue("Count", c.count); + } +}; + +auto cfg = VSilKit::Deserialize("Name: foo\nCount: 7\n"); +auto yaml = VSilKit::Serialize(cfg); +``` diff --git a/SilKit/include/silkit_yaml/RapidyamlImpl.cpp b/SilKit/include/silkit_yaml/RapidyamlImpl.cpp new file mode 100644 index 000000000..8525aeda2 --- /dev/null +++ b/SilKit/include/silkit_yaml/RapidyamlImpl.cpp @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2026 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +// This translation unit compiles the rapidyaml (single-header) implementation. +// +// rapidyaml is a single-header library: defining RYML_SINGLE_HDR_DEFINE_NOW +// before including the header pulls in its implementation. Add THIS file to +// exactly ONE target in your build so that the rapidyaml symbols +// (ryml::parse_in_arena, the emitters, the tree internals, ...) are defined +// exactly once. The SilKit::Yaml INTERFACE target supplies the include path and +// the required RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS=1 definition; make sure the +// target that compiles this file links SilKit::Yaml so it inherits both. +// +// If your project already compiles the rapidyaml implementation elsewhere, do +// NOT add this file (you would get duplicate-symbol linker errors). + +#define RYML_SINGLE_HDR_DEFINE_NOW +#include "rapidyaml.hpp" diff --git a/SilKit/include/silkit_yaml/YamlParserUtils.hpp b/SilKit/include/silkit_yaml/YamlParserUtils.hpp new file mode 100644 index 000000000..c6f65d0eb --- /dev/null +++ b/SilKit/include/silkit_yaml/YamlParserUtils.hpp @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: 2026 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#pragma once + +// UNSTABLE API: this auxiliary library does NOT carry the API/ABI stability +// guarantees of the main SIL Kit API (the silkit/ headers). It is provided to let +// out-of-tree projects parse their own YAML formats on their own schemata by +// reusing SIL Kit's thin rapidyaml wrapper. Pin your SIL Kit version. +// See silkit_yaml/README for details. + +#include +#include +#include +#include + +#include "silkit/participant/exception.hpp" + +#include "rapidyaml.hpp" + +namespace VSilKit { + +// Format a rapidyaml parse location and message into a SilKit::ConfigurationError. +inline auto MakeConfigurationError(ryml::Location location, + const std::string_view message) -> SilKit::ConfigurationError +{ + std::ostringstream s; + + s << "error parsing configuration"; + if (location.name.empty()) + { + s << " string: "; + } + else + { + s << " file " << location.name << ": "; + } + + s << "line " << (location.line + 1) << " column " << location.col << ": " << message; + + return SilKit::ConfigurationError{s.str()}; +} + +namespace detail { + +inline auto RapidyamlAllocate(const size_t length, void* /*hint*/, void* /*userData*/) -> void* +{ + return std::malloc(length); +} + +inline void RapidyamlFree(void* ptr, size_t /*length*/, void* /*userData*/) +{ + std::free(ptr); +} + +inline void RapidyamlError(const char* message, const size_t length, ryml::Location location, void* /*userData*/) +{ + const std::string_view rapidyamlMessage{message, length}; + throw VSilKit::MakeConfigurationError(location, rapidyamlMessage); +} + +} // namespace detail + +// Error-handling callbacks for a ryml::Tree/Parser. The error callback nicely +// formats the location and throws SilKit::ConfigurationError. Requires the +// compile definition RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS=1. +inline auto GetRapidyamlCallbacks() -> ryml::Callbacks +{ + return ryml::Callbacks{nullptr, detail::RapidyamlAllocate, detail::RapidyamlFree, detail::RapidyamlError}; +} + +} // namespace VSilKit diff --git a/SilKit/include/silkit_yaml/YamlSerdes.hpp b/SilKit/include/silkit_yaml/YamlSerdes.hpp new file mode 100644 index 000000000..1a1cccf3e --- /dev/null +++ b/SilKit/include/silkit_yaml/YamlSerdes.hpp @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2025 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#pragma once + +// UNSTABLE API: this auxiliary library does NOT carry the API/ABI stability +// guarantees of the main SIL Kit API (the silkit/ headers). Pin your SIL Kit +// version. See silkit_yaml/README for details. + +#include + +#include "silkit/participant/exception.hpp" + +#include "rapidyaml.hpp" + +#include "silkit_yaml/YamlParserUtils.hpp" + +namespace VSilKit { + +////////////////////////////////////////////////////////////////////// +// Generic YAML/JSON (de)serialization +// +// R must derive from VSilKit::BasicYamlReader and provide a +// void Read(T&) overload for the target type T. +// W must derive from VSilKit::BasicYamlWriter and provide a +// void Write(const T&) overload for the source type T. +////////////////////////////////////////////////////////////////////// + +template +auto Deserialize(const std::string& input) -> T +{ + if (input.empty()) + { + return {}; + } + + const auto rapidyamlCallbacks = VSilKit::GetRapidyamlCallbacks(); + + ryml::ParserOptions options{}; + options.locations(true); + + ryml::EventHandlerTree eventHandler{}; + auto parser = ryml::Parser(&eventHandler, options); + parser.reserve_locations(100u); + auto&& cinput = ryml::to_csubstr(input); + try + { + auto tree = ryml::parse_in_arena(&parser, cinput); + + // Install the error-handling callbacks. This will nicely format errors and throw an exception. + tree.callbacks(rapidyamlCallbacks); + + // Extract a reference to the root node of the document tree. + auto root = tree.crootref(); + + R reader{parser, root}; + T result{}; + reader.Read(result); + return result; + } + catch (const std::exception& ex) + { + throw SilKit::ConfigurationError{ex.what()}; + } + catch (...) + { + throw; + } +} + +template +auto Serialize(const T& input) -> std::string +{ + ryml::Tree t; + W writer{t.rootref()}; + writer.Write(input); + return ryml::emitrs_yaml(t); +} + +template +auto SerializeAsJson(const T& input) -> std::string +{ + ryml::Tree t; + W writer{t.rootref()}; + writer.Write(input); + return ryml::emitrs_json(t); +} + +} // namespace VSilKit diff --git a/SilKit/source/config/CMakeLists.txt b/SilKit/source/config/CMakeLists.txt index 1a877a489..c33d6298b 100644 --- a/SilKit/source/config/CMakeLists.txt +++ b/SilKit/source/config/CMakeLists.txt @@ -19,8 +19,8 @@ add_library(O_SilKit_Config OBJECT YamlWriter.hpp YamlWriter.cpp - YamlParserUtils.hpp - YamlParserUtils.cpp + CapabilitiesParser.hpp + CapabilitiesParser.cpp YamlValidator.hpp YamlValidator.cpp @@ -31,6 +31,68 @@ target_link_libraries(O_SilKit_Config ) +################################################################################ +# SilKit::Yaml -- header-only auxiliary YAML-parsing library for out-of-tree +# projects (unstable API/ABI). Opt-in via -DSILKIT_INSTALL_YAML=ON. +# +# The generic YAML machinery lives in the public sibling folder +# SilKit/include/silkit_yaml/ and is header-only. rapidyaml is a single-header +# library; consumers compile its implementation exactly once via the bundled +# silkit_yaml/RapidyamlImpl.cpp. Nothing is compiled here -- the INTERFACE target +# only carries usage requirements. +################################################################################ +if(SILKIT_INSTALL_YAML) + set(_silkit_yaml_rapidyaml_dir "${SILKIT_THIRD_PARTY_SOURCE_DIR}/rapidyaml") + + add_library(SilKitYaml INTERFACE) + add_library(SilKit::Yaml ALIAS SilKitYaml) + # Export as SilKit::Yaml (matching the in-tree alias) rather than the default + # SilKit::SilKitYaml derived from the target name. + set_target_properties(SilKitYaml PROPERTIES EXPORT_NAME Yaml) + + target_include_directories(SilKitYaml + INTERFACE + $ + $ + $ + $ # for unprefixed rapidyaml.hpp + ) + target_link_libraries(SilKitYaml INTERFACE SilKitInterface) # for silkit/participant/exception.hpp + target_compile_definitions(SilKitYaml INTERFACE RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS=1) + target_compile_features(SilKitYaml INTERFACE cxx_std_17) + + # Install the silkit_yaml/ headers + the impl TU (excluded from the default + # header glob in SilKit/include/CMakeLists.txt), the bundled rapidyaml header, + # and the exported INTERFACE target. + install( + DIRECTORY ${PROJECT_SOURCE_DIR}/include/silkit_yaml/ + DESTINATION ${INSTALL_INCLUDE_DIR}/silkit_yaml + COMPONENT yaml + FILES_MATCHING + PATTERN "*.hpp" + PATTERN "*.cpp" + PATTERN "*.md" + ) + install( + FILES ${_silkit_yaml_rapidyaml_dir}/rapidyaml.hpp + DESTINATION ${INSTALL_INCLUDE_DIR}/silkit_yaml + COMPONENT yaml + ) + install( + TARGETS SilKitYaml + EXPORT SilKitYamlTargets + COMPONENT yaml + ) + install( + EXPORT SilKitYamlTargets + DESTINATION ${INSTALL_LIB_DIR}/cmake/SilKit + NAMESPACE "SilKit::" + FILE SilKitYamlTargets.cmake + COMPONENT yaml + ) +endif() + + add_silkit_test_to_executable(SilKitUnitTests SOURCES Test_Validation.cpp LIBS O_SilKit_Config S_SilKitImpl diff --git a/SilKit/source/config/CapabilitiesParser.cpp b/SilKit/source/config/CapabilitiesParser.cpp new file mode 100644 index 000000000..f39779fc3 --- /dev/null +++ b/SilKit/source/config/CapabilitiesParser.cpp @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2026 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "config/CapabilitiesParser.hpp" + +#include "silkit/participant/exception.hpp" + +#include "rapidyaml.hpp" + +namespace VSilKit { + +auto ParseCapabilities(const std::string& input) -> std::vector> +{ + std::vector> result; + auto&& cinput = ryml::to_csubstr(input); + auto t = ryml::parse_in_arena(cinput); + + auto root = t.crootref(); + if (!root.is_seq()) + { + throw SilKit::ConfigurationError{"First element in Capabilities string is not a sequence"}; + } + if (root.has_children()) + { + for (auto&& child : root.children()) + { + if (!child.is_map()) + { + throw SilKit::ConfigurationError{"Capabilities should be a sequence of map objects."}; + } + } + } + root >> result; + return result; +} + +} // namespace VSilKit diff --git a/SilKit/source/config/YamlParserUtils.hpp b/SilKit/source/config/CapabilitiesParser.hpp similarity index 60% rename from SilKit/source/config/YamlParserUtils.hpp rename to SilKit/source/config/CapabilitiesParser.hpp index 9adba1d4f..424eb9558 100644 --- a/SilKit/source/config/YamlParserUtils.hpp +++ b/SilKit/source/config/CapabilitiesParser.hpp @@ -8,17 +8,9 @@ #include #include -#include "silkit/participant/exception.hpp" - -#include "rapidyaml.hpp" - namespace VSilKit { // Utility for parsing key-value lists for protocol capabilities auto ParseCapabilities(const std::string& input) -> std::vector>; -auto MakeConfigurationError(ryml::Location location, const std::string_view message) -> SilKit::ConfigurationError; - -auto GetRapidyamlCallbacks() -> ryml::Callbacks; - } // namespace VSilKit diff --git a/SilKit/source/config/YamlParser.hpp b/SilKit/source/config/YamlParser.hpp index 7ccfee96c..91d1f31d4 100644 --- a/SilKit/source/config/YamlParser.hpp +++ b/SilKit/source/config/YamlParser.hpp @@ -7,7 +7,8 @@ #include "config/YamlReader.hpp" #include "config/YamlWriter.hpp" -#include "config/YamlParserUtils.hpp" + +#include "silkit_yaml/YamlSerdes.hpp" #include "rapidyaml.hpp" @@ -17,67 +18,27 @@ namespace Config { ////////////////////////////////////////////////////////////////////// // Configuration Parsing +// +// Thin wrappers around the generic VSilKit serdes templates, defaulting to the +// SilKit concrete reader/writer for the in-tree configuration types. ////////////////////////////////////////////////////////////////////// template auto Deserialize(const std::string& input) -> T { - if (input.empty()) - { - return {}; - } - - const auto rapidyamlCallbacks = VSilKit::GetRapidyamlCallbacks(); - - ryml::ParserOptions options{}; - options.locations(true); - - ryml::EventHandlerTree eventHandler{}; - auto parser = ryml::Parser(&eventHandler, options); - parser.reserve_locations(100u); - auto&& cinput = ryml::to_csubstr(input); - try - { - auto tree = ryml::parse_in_arena(&parser, cinput); - - // Install the error-handling callbacks. This will nicely format errors and throw an exception. - tree.callbacks(rapidyamlCallbacks); - - // Extract a reference to the root node of the document tree. - auto root = tree.crootref(); - - R reader{parser, root}; - T result{}; - reader.Read(result); - return result; - } - catch (const std::exception& ex) - { - throw SilKit::ConfigurationError{ex.what()}; - } - catch (...) - { - throw; - } + return VSilKit::Deserialize(input); } - template auto Serialize(const T& input) -> std::string { - ryml::Tree t; - W writer{t.rootref()}; - writer.Write(input); - return ryml::emitrs_yaml(t); + return VSilKit::Serialize(input); } template auto SerializeAsJson(const T& input) -> std::string { - ryml::Tree t; - W writer{t.rootref()}; - writer.Write(input); - return ryml::emitrs_json(t); + return VSilKit::SerializeAsJson(input); } } // namespace Config diff --git a/SilKit/source/config/YamlParserUtils.cpp b/SilKit/source/config/YamlParserUtils.cpp deleted file mode 100644 index 60a462622..000000000 --- a/SilKit/source/config/YamlParserUtils.cpp +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Vector Informatik GmbH -// -// SPDX-License-Identifier: MIT - -#include "config/YamlParserUtils.hpp" - -#include "silkit/participant/exception.hpp" - -#include - - -namespace { - -auto RapidyamlAllocate(size_t length, void* hint, void* userData) -> void*; -void RapidyamlFree(void* ptr, size_t length, void* userData); -void RapidyamlError(const char* message, size_t length, ryml::Location location, void* userData); - -} // namespace - - -namespace VSilKit { - -auto ParseCapabilities(const std::string& input) -> std::vector> -{ - std::vector> result; - auto&& cinput = ryml::to_csubstr(input); - auto t = ryml::parse_in_arena(cinput); - - auto root = t.crootref(); - if (!root.is_seq()) - { - throw SilKit::ConfigurationError{"First element in Capabilities string is not a sequence"}; - } - if (root.has_children()) - { - for (auto&& child : root.children()) - { - if (!child.is_map()) - { - throw SilKit::ConfigurationError{"Capabilities should be a sequence of map objects."}; - } - } - } - root >> result; - return result; -} - -auto MakeConfigurationError(ryml::Location location, const std::string_view message) -> SilKit::ConfigurationError -{ - std::ostringstream s; - - s << "error parsing configuration"; - if (location.name.empty()) - { - s << " string: "; - } - else - { - s << " file " << location.name << ": "; - } - - s << "line " << (location.line + 1) << " column " << location.col << ": " << message; - - return SilKit::ConfigurationError{s.str()}; -} - -auto GetRapidyamlCallbacks() -> ryml::Callbacks -{ - return ryml::Callbacks{nullptr, RapidyamlAllocate, RapidyamlFree, RapidyamlError}; -} - -} // namespace VSilKit - - -namespace { - -auto RapidyamlAllocate(const size_t length, void* /*hint*/, void* /*userData*/) -> void* -{ - return std::malloc(length); -} - -void RapidyamlFree(void* ptr, size_t /*length*/, void* /*userData*/) -{ - std::free(ptr); -} - -void RapidyamlError(const char* message, const size_t length, ryml::Location location, void* /*userData*/) -{ - const std::string_view rapidyamlMessage{message, length}; - throw VSilKit::MakeConfigurationError(location, message); -} - -} // namespace diff --git a/SilKit/source/config/YamlReader.hpp b/SilKit/source/config/YamlReader.hpp index 415fb23b7..0af55f3e7 100644 --- a/SilKit/source/config/YamlReader.hpp +++ b/SilKit/source/config/YamlReader.hpp @@ -13,256 +13,12 @@ #include "rapidyaml.hpp" +#include "silkit_yaml/BasicYamlReader.hpp" + #include "config/ParticipantConfiguration.hpp" -#include "config/YamlParserUtils.hpp" namespace VSilKit { -template -class BasicYamlReader -{ -protected: - ryml::Parser& _parser; - ryml::ConstNodeRef _node; - -public: - BasicYamlReader(ryml::Parser& parser_, ryml::ConstNodeRef node_) - : _parser(parser_) - , _node(node_) - { - } - -public: - template , bool> = true> - void OptionalRead(T& val, const std::string& name) - { - auto&& child = GetChildSafe(name); - if (child.IsValid()) - { - child.Read(val); - } - } - - void OptionalRead(bool& val, const std::string& name) - { - auto&& child = GetChildSafe(name); - if (child.IsValid()) - { - child.Read(val); - } - } - - template - void OptionalRead(std::optional& val, const std::string& name) - { - auto&& child = GetChildSafe(name); - if (child.IsValid()) - { - T tmp{}; - child.Read(tmp); - val = std::move(tmp); // needs a proper setter to set "has_value" - } - } - - - template , bool> = true> - void OptionalRead(T& val, const std::string& name) - { - auto tmp = ryml::fmt::overflow_checked(val); - (void)_node.get_if(ryml::to_csubstr(name), &tmp); - } - - template - void OptionalRead_deprecated_alternative(ConfigT& value, const std::string& fieldName, - std::initializer_list deprecatedFieldNames) - { - if (!(IsMap() || IsSequence())) - { - return; - } - - auto hasChild = [this](auto&& name) { - auto&& c = GetChildSafe(name); - return c.IsValid(); - }; - - std::vector presentDeprecatedFieldNames; - std::copy_if(deprecatedFieldNames.begin(), deprecatedFieldNames.end(), - std::back_inserter(presentDeprecatedFieldNames), hasChild); - - if (HasKey(fieldName) && presentDeprecatedFieldNames.size() >= 1) - { - std::stringstream ss; - ss << "The key \"" << fieldName << "\" and the deprecated alternatives"; - for (const auto& deprecatedFieldName : presentDeprecatedFieldNames) - { - ss << " \"" << deprecatedFieldName << "\""; - } - ss << " are present."; - throw SilKit::ConfigurationError{ss.str()}; - } - - if (presentDeprecatedFieldNames.size() >= 2) - { - std::stringstream ss; - ss << "The deprecated keys"; - for (const auto& deprecatedFieldName : presentDeprecatedFieldNames) - { - ss << " \"" << deprecatedFieldName << "\""; - } - ss << " are present."; - throw SilKit::ConfigurationError{ss.str()}; - } - - OptionalRead(value, fieldName); - for (const auto& deprecatedFieldName : deprecatedFieldNames) - { - OptionalRead(value, deprecatedFieldName); - } - } - -public: - template - void ReadKeyValue(T& value, const std::string& name) - { - auto&& child = GetChildSafe(name); - - if (!child.IsValid()) - { - std::ostringstream s; - s << "missing key: " << name; - throw MakeConfigurationError(s.str()); - } - - child.Read(value); - } - -public: - template - void Read(T& value) - { - _node >> value; - } - - template - void Read(std::chrono::duration& obj) - { - Rep value{}; - auto tmp = ryml::fmt::overflow_checked(value); - Read(tmp); - obj = std::chrono::milliseconds{value}; - } - - template - void Read(std::vector& val) - { - if (!IsSequence()) - { - throw MakeConfigurationError("Expected a sequence."); - } - - for (auto&& i : _node.cchildren()) - { - T element{}; - MakeImpl(i).Read(element); - val.emplace_back(std::move(element)); - } - } - -protected: - bool IsValid() const - { - return !_node.invalid(); - } - - bool IsMap() const - { - return _node.is_map(); - } - - bool IsScalar() const - { - return _node.is_val() || _node.is_keyval(); - } - - bool IsSequence() const - { - return _node.is_seq(); - } - - bool IsExistingString(const char* str) const - { - if (!_node.is_val()) - { - return false; - } - return _node.val() == ryml::to_csubstr(str); - } - - bool IsEmpty() const - { - return _node.empty(); - } - - bool IsString(const char* string) const - { - return IsScalar() && (_node.val() == ryml::to_csubstr(string)); - } - - bool HasKey(const std::string& name) const - { - return HasKey(_node, name); - } - - static bool HasKey(ryml::ConstNodeRef node, const std::string& name) - { - return node.is_map() && !node.find_child(ryml::to_csubstr(name)).invalid(); - } - - auto MakeConfigurationError(const std::string_view message) const -> SilKit::ConfigurationError - { - const auto location = _parser.location(_node); - return VSilKit::MakeConfigurationError(location, message); - } - - auto GetChildSafe(const std::string& name) const -> Impl - { - if (HasKey(name)) - { - return MakeImpl(_node.find_child(ryml::to_csubstr(name))); - } - - if (IsSequence()) - { - for (const auto& child : _node.cchildren()) - { - if (child.is_container() && HasKey(child, name)) - { - return MakeImpl(child.find_child(ryml::to_csubstr(name))); - } - } - } - - return MakeImpl({}); - } - - auto MakeImpl(ryml::ConstNodeRef node_) const -> Impl - { - return Impl{_parser, node_}; - } - -private: - auto AsImpl() -> Impl& - { - return static_cast(*this); - } - - auto AsImpl() const -> const Impl& - { - return static_cast(*this); - } -}; - struct YamlReader : BasicYamlReader { using BasicYamlReader::BasicYamlReader; diff --git a/SilKit/source/config/YamlWriter.hpp b/SilKit/source/config/YamlWriter.hpp index 0fb05c8ff..dec13f500 100644 --- a/SilKit/source/config/YamlWriter.hpp +++ b/SilKit/source/config/YamlWriter.hpp @@ -12,119 +12,12 @@ #include "rapidyaml.hpp" +#include "silkit_yaml/BasicYamlWriter.hpp" + #include "config/ParticipantConfiguration.hpp" namespace VSilKit { -template -struct BasicYamlWriter -{ - ryml::NodeRef node; - -public: - BasicYamlWriter(ryml::NodeRef node_) - : node(node_) - { - } - -public: - template - void OptionalWrite(const std::optional& val, const std::string& name) - { - if (val.has_value()) - { - WriteKeyValue(name, val.value()); - } - } - - template - void OptionalWrite(const std::vector& val, const std::string& name) - { - if (!val.empty()) - { - WriteKeyValue(name, val); - } - } - - void OptionalWrite(const std::string& val, const std::string& name) - { - if (!val.empty()) - { - WriteKeyValue(name, val); - } - } - - template - void NonDefaultWrite(const T& val, const std::string& name, const T& defaultValue) - { - if (!(val == defaultValue)) - { - WriteKeyValue(name, val); - } - } - - template - void WriteKeyValue(const std::string& name, const T& val) - { - if (!node.is_map()) - { - throw SilKit::ConfigurationError("Parse error: trying to access child of something not a map"); - } - - auto writer = MakeImpl(node.append_child() << ryml::key(name)); - writer.Write(val); - } - - template - void Write(const T& val) - { - node << val; - } - - template - void Write(const std::vector& val) - { - node |= ryml::SEQ; - for (auto&& el : val) - { - auto writer = MakeImpl(node.append_child()); - writer.Write(el); - } - } - -protected: - void MakeMap() - { - node |= ryml::MAP; - } - - auto MakeConfigurationError(const char* message) const -> SilKit::ConfigurationError - { - std::ostringstream s; - - s << "error writing configuration: " << message; - - return SilKit::ConfigurationError{s.str()}; - } - -protected: - auto MakeImpl(ryml::NodeRef node_) const -> Impl - { - return Impl{node_}; - } - -private: - auto AsImpl() -> Impl& - { - return static_cast(*this); - } - - auto AsImpl() const -> const Impl& - { - return static_cast(*this); - } -}; - struct YamlWriter : BasicYamlWriter { using BasicYamlWriter::BasicYamlWriter; diff --git a/SilKit/source/core/vasio/VAsioCapabilities.cpp b/SilKit/source/core/vasio/VAsioCapabilities.cpp index 39906da0e..2451817e2 100644 --- a/SilKit/source/core/vasio/VAsioCapabilities.cpp +++ b/SilKit/source/core/vasio/VAsioCapabilities.cpp @@ -6,7 +6,7 @@ #include "silkit/participant/exception.hpp" -#include "config/YamlParser.hpp" +#include "config/CapabilitiesParser.hpp" #include #include From d1d51cee19b9091f714ee61a33bbb2402d9e744d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20B=C3=B6rschig?= Date: Tue, 30 Jun 2026 17:09:45 +0200 Subject: [PATCH 2/7] fixup! config: factor yaml parsing out into a separate silkit_yaml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit move to SilKitYaml Signed-off-by: Marius Börschig --- CMakeLists.txt | 6 +- SilKit/cmake/SilKitConfig.cmake.in | 6 -- SilKit/include/CMakeLists.txt | 9 +- SilKit/include/silkit_yaml/README.md | 94 ------------------- SilKit/include/silkit_yaml/RapidyamlImpl.cpp | 19 ---- SilKit/source/CMakeLists.txt | 8 ++ SilKit/source/config/CMakeLists.txt | 62 ------------ SilKit/source/config/YamlParser.hpp | 37 ++++++-- SilKit/source/config/YamlReader.cpp | 34 +++---- SilKit/source/config/YamlReader.hpp | 4 +- SilKit/source/config/YamlWriter.hpp | 4 +- SilKitYaml/CMakeLists.txt | 31 ++++++ SilKitYaml/README.md | 73 ++++++++++++++ .../include/SilKitYaml}/BasicYamlReader.hpp | 25 +++-- .../include/SilKitYaml}/BasicYamlWriter.hpp | 20 ++-- SilKitYaml/include/SilKitYaml/YamlError.hpp | 39 ++++++++ .../include/SilKitYaml}/YamlParserUtils.hpp | 32 +++---- .../include/SilKitYaml}/YamlSerdes.hpp | 23 +++-- 18 files changed, 256 insertions(+), 270 deletions(-) delete mode 100644 SilKit/include/silkit_yaml/README.md delete mode 100644 SilKit/include/silkit_yaml/RapidyamlImpl.cpp create mode 100644 SilKitYaml/CMakeLists.txt create mode 100644 SilKitYaml/README.md rename {SilKit/include/silkit_yaml => SilKitYaml/include/SilKitYaml}/BasicYamlReader.hpp (89%) rename {SilKit/include/silkit_yaml => SilKitYaml/include/SilKitYaml}/BasicYamlWriter.hpp (78%) create mode 100644 SilKitYaml/include/SilKitYaml/YamlError.hpp rename {SilKit/include/silkit_yaml => SilKitYaml/include/SilKitYaml}/YamlParserUtils.hpp (54%) rename {SilKit/include/silkit_yaml => SilKitYaml/include/SilKitYaml}/YamlSerdes.hpp (75%) diff --git a/CMakeLists.txt b/CMakeLists.txt index e7613f174..4b22f4969 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,6 @@ option(SILKIT_USE_SYSTEM_LIBRARIES "Use the libraries installed on the system fo option(SILKIT_BUILD_REPRODUCIBLE "Creates a reproducible build by ommiting timestamps/unique build ids" ON) option(SILKIT_BUILD_LINUX_PACKAGE "Creates SIL Kit builds suitable for package managers in Linux Distributions (.deb)" OFF) option(SILKIT_BUILD_LTO "Build with link time optimizations" OFF) -option(SILKIT_INSTALL_YAML "Install the SilKit::Yaml header-only auxiliary YAML library (silkit_yaml; unstable API/ABI)" OFF) if(SILKIT_BUILD_LINUX_PACKAGE) @@ -134,6 +133,10 @@ if(MINGW) endif() endif() +# Internal, self-contained YAML helper library (header-only). Must come before +# SilKit, which depends on it, and after ThirdParty, which provides rapidyaml. +add_subdirectory(SilKitYaml) + # Have both SIL Kit library and demo project in a single solution add_subdirectory(SilKit) if(SILKIT_BUILD_UTILITIES) @@ -159,6 +162,7 @@ if(SILKIT_INSTALL_SOURCE) ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_CURRENT_SOURCE_DIR}/Demos ${CMAKE_CURRENT_SOURCE_DIR}/SilKit + ${CMAKE_CURRENT_SOURCE_DIR}/SilKitYaml ${CMAKE_CURRENT_SOURCE_DIR}/Utilities DESTINATION ${INSTALL_SOURCE_DIR} COMPONENT source diff --git a/SilKit/cmake/SilKitConfig.cmake.in b/SilKit/cmake/SilKitConfig.cmake.in index 7d4282652..e1de372e9 100644 --- a/SilKit/cmake/SilKitConfig.cmake.in +++ b/SilKit/cmake/SilKitConfig.cmake.in @@ -11,9 +11,3 @@ find_dependency(Threads) get_filename_component(SILKIT_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) include("${SILKIT_CMAKE_DIR}/SilKitTargets.cmake") - -# Optional unstable auxiliary YAML library (present only if the package was built -# with -DSILKIT_INSTALL_YAML=ON). Exposes the SilKit::Yaml target. -if(EXISTS "${SILKIT_CMAKE_DIR}/SilKitYamlTargets.cmake") - include("${SILKIT_CMAKE_DIR}/SilKitYamlTargets.cmake") -endif() diff --git a/SilKit/include/CMakeLists.txt b/SilKit/include/CMakeLists.txt index 16571e9fd..55957a820 100644 --- a/SilKit/include/CMakeLists.txt +++ b/SilKit/include/CMakeLists.txt @@ -30,9 +30,7 @@ install( COMPONENT dev INCLUDES DESTINATION ${INSTALL_INCLUDE_DIR} # Destination for header files ) -# Copy all headers from the source directory to the proper destination. -# The silkit_yaml/ folder is the unstable auxiliary YAML library; it is installed -# separately and only when -DSILKIT_INSTALL_YAML=ON (see source/config/CMakeLists.txt). +# Copy all headers from the source directory to the proper destination install( DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/ DESTINATION ${INSTALL_INCLUDE_DIR} @@ -40,14 +38,11 @@ install( FILES_MATCHING PATTERN *.hpp PATTERN *.ipp - PATTERN "silkit_yaml" EXCLUDE ) # for C API install( DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/ DESTINATION ${INSTALL_INCLUDE_DIR} COMPONENT dev - FILES_MATCHING - PATTERN *.h - PATTERN "silkit_yaml" EXCLUDE + FILES_MATCHING PATTERN *.h ) diff --git a/SilKit/include/silkit_yaml/README.md b/SilKit/include/silkit_yaml/README.md deleted file mode 100644 index d1eba6812..000000000 --- a/SilKit/include/silkit_yaml/README.md +++ /dev/null @@ -1,94 +0,0 @@ - - -# SilKit::Yaml — auxiliary YAML parsing for out-of-tree projects - -This is a **header-only auxiliary library** that exposes the generic YAML -machinery used internally by SIL Kit's configuration parser, so that projects -living outside the SIL Kit tree can parse **their own** YAML formats against -**their own** schemata. It is a thin wrapper around -[rapidyaml](https://github.com/biojppm/rapidyaml). - -## Stability - -> **UNSTABLE API/ABI.** Unlike the main SIL Kit API (the `silkit/` headers), -> this library does **not** carry any source- or binary-compatibility guarantee -> across SIL Kit versions. The API lives in the internal `VSilKit` namespace and -> may change at any time. **Pin the exact SIL Kit version** you build against. - -The bundled `rapidyaml.hpp` is a specific vendored snapshot; the templates are -written against exactly that version. Parsing errors are reported by throwing -`SilKit::ConfigurationError` (from `silkit/participant/exception.hpp`). - -## What's here - -| Header | Purpose | -|---|---| -| `BasicYamlReader.hpp` | `VSilKit::BasicYamlReader` CRTP base for deserialization | -| `BasicYamlWriter.hpp` | `VSilKit::BasicYamlWriter` CRTP base for serialization | -| `YamlSerdes.hpp` | `VSilKit::Deserialize` / `Serialize` / `SerializeAsJson` | -| `YamlParserUtils.hpp` | rapidyaml error callbacks + `MakeConfigurationError` | -| `rapidyaml.hpp` | the bundled rapidyaml single header | -| `RapidyamlImpl.cpp` | the one TU that compiles the rapidyaml implementation | - -## Usage - -Build SIL Kit with `-DSILKIT_INSTALL_YAML=ON` so these files are installed and -the `SilKit::Yaml` CMake target is exported. - -```cmake -find_package(SilKit REQUIRED) -add_executable(my_tool - main.cpp - ${SILKIT_YAML_INCLUDE_DIR}/silkit_yaml/RapidyamlImpl.cpp # compile ryml impl ONCE -) -target_link_libraries(my_tool PRIVATE SilKit::Yaml) -``` - -`SilKit::Yaml` carries the include paths and the required -`RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS=1` definition. `RapidyamlImpl.cpp` must be -compiled in **exactly one** target — omit it if you already compile rapidyaml -elsewhere. - -```cpp -#include "silkit_yaml/BasicYamlReader.hpp" -#include "silkit_yaml/BasicYamlWriter.hpp" -#include "silkit_yaml/YamlSerdes.hpp" - -struct MyConfig -{ - std::string name; - int count{}; -}; - -struct MyReader : VSilKit::BasicYamlReader -{ - using BasicYamlReader::BasicYamlReader; - using BasicYamlReader::Read; - - void Read(MyConfig& c) - { - ReadKeyValue(c.name, "Name"); - OptionalRead(c.count, "Count"); - } -}; - -struct MyWriter : VSilKit::BasicYamlWriter -{ - using BasicYamlWriter::BasicYamlWriter; - using BasicYamlWriter::Write; - - void Write(const MyConfig& c) - { - MakeMap(); - WriteKeyValue("Name", c.name); - WriteKeyValue("Count", c.count); - } -}; - -auto cfg = VSilKit::Deserialize("Name: foo\nCount: 7\n"); -auto yaml = VSilKit::Serialize(cfg); -``` diff --git a/SilKit/include/silkit_yaml/RapidyamlImpl.cpp b/SilKit/include/silkit_yaml/RapidyamlImpl.cpp deleted file mode 100644 index 8525aeda2..000000000 --- a/SilKit/include/silkit_yaml/RapidyamlImpl.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Vector Informatik GmbH -// -// SPDX-License-Identifier: MIT - -// This translation unit compiles the rapidyaml (single-header) implementation. -// -// rapidyaml is a single-header library: defining RYML_SINGLE_HDR_DEFINE_NOW -// before including the header pulls in its implementation. Add THIS file to -// exactly ONE target in your build so that the rapidyaml symbols -// (ryml::parse_in_arena, the emitters, the tree internals, ...) are defined -// exactly once. The SilKit::Yaml INTERFACE target supplies the include path and -// the required RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS=1 definition; make sure the -// target that compiles this file links SilKit::Yaml so it inherits both. -// -// If your project already compiles the rapidyaml implementation elsewhere, do -// NOT add this file (you would get duplicate-symbol linker errors). - -#define RYML_SINGLE_HDR_DEFINE_NOW -#include "rapidyaml.hpp" diff --git a/SilKit/source/CMakeLists.txt b/SilKit/source/CMakeLists.txt index 930af61ad..ebe1e1155 100644 --- a/SilKit/source/CMakeLists.txt +++ b/SilKit/source/CMakeLists.txt @@ -115,6 +115,14 @@ if (TARGET rapidyaml) ) endif() +if (TARGET SilKitYaml) + # Internal, header-only YAML helper. BUILD_INTERFACE only -- it is not + # installed and its symbols are not exported from the SIL Kit library. + target_link_libraries(I_SilKit + INTERFACE $ + ) +endif() + add_library(O_SilKit_CreateParticipantImpl OBJECT CreateParticipantImpl.hpp diff --git a/SilKit/source/config/CMakeLists.txt b/SilKit/source/config/CMakeLists.txt index c33d6298b..29f25c517 100644 --- a/SilKit/source/config/CMakeLists.txt +++ b/SilKit/source/config/CMakeLists.txt @@ -31,68 +31,6 @@ target_link_libraries(O_SilKit_Config ) -################################################################################ -# SilKit::Yaml -- header-only auxiliary YAML-parsing library for out-of-tree -# projects (unstable API/ABI). Opt-in via -DSILKIT_INSTALL_YAML=ON. -# -# The generic YAML machinery lives in the public sibling folder -# SilKit/include/silkit_yaml/ and is header-only. rapidyaml is a single-header -# library; consumers compile its implementation exactly once via the bundled -# silkit_yaml/RapidyamlImpl.cpp. Nothing is compiled here -- the INTERFACE target -# only carries usage requirements. -################################################################################ -if(SILKIT_INSTALL_YAML) - set(_silkit_yaml_rapidyaml_dir "${SILKIT_THIRD_PARTY_SOURCE_DIR}/rapidyaml") - - add_library(SilKitYaml INTERFACE) - add_library(SilKit::Yaml ALIAS SilKitYaml) - # Export as SilKit::Yaml (matching the in-tree alias) rather than the default - # SilKit::SilKitYaml derived from the target name. - set_target_properties(SilKitYaml PROPERTIES EXPORT_NAME Yaml) - - target_include_directories(SilKitYaml - INTERFACE - $ - $ - $ - $ # for unprefixed rapidyaml.hpp - ) - target_link_libraries(SilKitYaml INTERFACE SilKitInterface) # for silkit/participant/exception.hpp - target_compile_definitions(SilKitYaml INTERFACE RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS=1) - target_compile_features(SilKitYaml INTERFACE cxx_std_17) - - # Install the silkit_yaml/ headers + the impl TU (excluded from the default - # header glob in SilKit/include/CMakeLists.txt), the bundled rapidyaml header, - # and the exported INTERFACE target. - install( - DIRECTORY ${PROJECT_SOURCE_DIR}/include/silkit_yaml/ - DESTINATION ${INSTALL_INCLUDE_DIR}/silkit_yaml - COMPONENT yaml - FILES_MATCHING - PATTERN "*.hpp" - PATTERN "*.cpp" - PATTERN "*.md" - ) - install( - FILES ${_silkit_yaml_rapidyaml_dir}/rapidyaml.hpp - DESTINATION ${INSTALL_INCLUDE_DIR}/silkit_yaml - COMPONENT yaml - ) - install( - TARGETS SilKitYaml - EXPORT SilKitYamlTargets - COMPONENT yaml - ) - install( - EXPORT SilKitYamlTargets - DESTINATION ${INSTALL_LIB_DIR}/cmake/SilKit - NAMESPACE "SilKit::" - FILE SilKitYamlTargets.cmake - COMPONENT yaml - ) -endif() - - add_silkit_test_to_executable(SilKitUnitTests SOURCES Test_Validation.cpp LIBS O_SilKit_Config S_SilKitImpl diff --git a/SilKit/source/config/YamlParser.hpp b/SilKit/source/config/YamlParser.hpp index 91d1f31d4..b3db0a63d 100644 --- a/SilKit/source/config/YamlParser.hpp +++ b/SilKit/source/config/YamlParser.hpp @@ -5,10 +5,12 @@ #include +#include "silkit/participant/exception.hpp" + #include "config/YamlReader.hpp" #include "config/YamlWriter.hpp" -#include "silkit_yaml/YamlSerdes.hpp" +#include "SilKitYaml/YamlSerdes.hpp" #include "rapidyaml.hpp" @@ -19,26 +21,49 @@ namespace Config { ////////////////////////////////////////////////////////////////////// // Configuration Parsing // -// Thin wrappers around the generic VSilKit serdes templates, defaulting to the -// SilKit concrete reader/writer for the in-tree configuration types. +// Thin wrappers around the generic SilKitYaml serdes templates, defaulting to the +// SilKit concrete reader/writer for the in-tree configuration types. They form +// the boundary that translates SilKitYaml::YamlError into the public +// SilKit::ConfigurationError, preserving SIL Kit's exception contract. ////////////////////////////////////////////////////////////////////// template auto Deserialize(const std::string& input) -> T { - return VSilKit::Deserialize(input); + try + { + return SilKitYaml::Deserialize(input); + } + catch (const std::exception& ex) + { + throw SilKit::ConfigurationError{ex.what()}; + } } template auto Serialize(const T& input) -> std::string { - return VSilKit::Serialize(input); + try + { + return SilKitYaml::Serialize(input); + } + catch (const std::exception& ex) + { + throw SilKit::ConfigurationError{ex.what()}; + } } template auto SerializeAsJson(const T& input) -> std::string { - return VSilKit::SerializeAsJson(input); + try + { + return SilKitYaml::SerializeAsJson(input); + } + catch (const std::exception& ex) + { + throw SilKit::ConfigurationError{ex.what()}; + } } } // namespace Config diff --git a/SilKit/source/config/YamlReader.cpp b/SilKit/source/config/YamlReader.cpp index d85639a11..ced45a07d 100644 --- a/SilKit/source/config/YamlReader.cpp +++ b/SilKit/source/config/YamlReader.cpp @@ -23,7 +23,7 @@ void YamlReader::Read(SilKit::Services::MatchingLabel::Kind& value) else if (numericValue == 1) value = SilKit::Services::MatchingLabel::Kind::Optional; else - throw MakeConfigurationError("MatchingLabel::Kind should be an integer of Mandatory(2)|Optional(1)."); + throw MakeError("MatchingLabel::Kind should be an integer of Mandatory(2)|Optional(1)."); } @@ -45,7 +45,7 @@ void YamlReader::Read(SilKit::Services::Logging::Level& obj) obj = SilKit::Services::Logging::Level::Off; else { - throw MakeConfigurationError("Unknown SilKit::Services::Logging::Level."); + throw MakeError("Unknown SilKit::Services::Logging::Level."); } } @@ -53,7 +53,7 @@ void YamlReader::Read(SilKit::Services::Logging::Topic& obj) { if (!IsScalar() ) { - throw MakeConfigurationError("Topic should be a string."); + throw MakeError("Topic should be a string."); } std::string value; @@ -63,7 +63,7 @@ void YamlReader::Read(SilKit::Services::Logging::Topic& obj) if (topic == SilKit::Services::Logging::Topic::Invalid) { - throw MakeConfigurationError("Unknown SilKit::Services::Logging::Topic"); + throw MakeError("Unknown SilKit::Services::Logging::Topic"); } obj = topic; } @@ -145,7 +145,7 @@ void YamlReader::Read(SilKit::Services::Flexray::FlexrayChannel& obj) obj = SilKit::Services::Flexray::FlexrayChannel::None; else { - throw MakeConfigurationError("Unknown Services::Flexray::FlexrayChannel"); + throw MakeError("Unknown Services::Flexray::FlexrayChannel"); } } @@ -159,7 +159,7 @@ void YamlReader::Read(SilKit::Services::Flexray::FlexrayClockPeriod& obj) obj = SilKit::Services::Flexray::FlexrayClockPeriod::T50NS; else { - throw MakeConfigurationError("Unknown Services::Flexray::FlexrayClockPeriod"); + throw MakeError("Unknown Services::Flexray::FlexrayClockPeriod"); } } @@ -171,7 +171,7 @@ void YamlReader::Read(SilKit::Services::Flexray::FlexrayTransmissionMode& obj) obj = SilKit::Services::Flexray::FlexrayTransmissionMode::SingleShot; else { - throw MakeConfigurationError("Unknown Services::Flexray::FlexrayTransmissionMode."); + throw MakeError("Unknown Services::Flexray::FlexrayTransmissionMode."); } } @@ -191,7 +191,7 @@ void YamlReader::Read(SilKit::Config::Sink::Type& obj) } else { - throw MakeConfigurationError("Unknown Sink::Type"); + throw MakeError("Unknown Sink::Type"); } } @@ -207,7 +207,7 @@ void YamlReader::Read(SilKit::Config::Sink::Format& obj) } else { - throw MakeConfigurationError("Unknown Sink::Format:"); + throw MakeError("Unknown Sink::Format:"); } } @@ -228,7 +228,7 @@ void YamlReader::Read(SilKit::Config::Sink& obj) { if (!HasKey("LogName")) { - throw MakeConfigurationError("Sink of type Sink::Type::File requires a LogName"); + throw MakeError("Sink of type Sink::Type::File requires a LogName"); } ReadKeyValue(obj.logName, "LogName"); @@ -258,7 +258,7 @@ void YamlReader::Read(SilKit::Config::MetricsSink::Type& obj) } else { - throw MakeConfigurationError("Unknown MetricsSink::Type"); + throw MakeError("Unknown MetricsSink::Type"); } } @@ -278,7 +278,7 @@ void YamlReader::Read(SilKit::Config::MdfChannel& obj) { if (!IsMap()) { - throw MakeConfigurationError("MdfChannel should be a Map"); + throw MakeError("MdfChannel should be a Map"); } OptionalRead(obj.channelName, "ChannelName"); OptionalRead(obj.channelPath, "ChannelPath"); @@ -307,7 +307,7 @@ void YamlReader::Read(SilKit::Config::Replay::Direction& obj) obj = SilKit::Config::Replay::Direction::Both; else { - throw MakeConfigurationError("Unknown Replay::Direction"); + throw MakeError("Unknown Replay::Direction"); } } void YamlReader::Read(SilKit::Config::CanController& obj) @@ -341,7 +341,7 @@ void YamlReader::Read(SilKit::Config::Label::Kind& obj) obj = SilKit::Config::Label::Kind::Optional; else { - throw MakeConfigurationError("Unknown Label::Kind"); + throw MakeError("Unknown Label::Kind"); } } @@ -415,7 +415,7 @@ void YamlReader::Read(SilKit::Config::TraceSink::Type& obj) obj = SilKit::Config::TraceSink::Type::PcapPipe; else { - throw MakeConfigurationError("Unknown TraceSink::Type"); + throw MakeError("Unknown TraceSink::Type"); } } void YamlReader::Read(SilKit::Config::TraceSource& obj) @@ -435,7 +435,7 @@ void YamlReader::Read(SilKit::Config::TraceSource::Type& obj) obj = SilKit::Config::TraceSource::Type::PcapFile; else { - throw MakeConfigurationError("Unknown TraceSource::Type:"); + throw MakeError("Unknown TraceSource::Type:"); } } @@ -475,7 +475,7 @@ void YamlReader::Read(SilKit::Config::Aggregation& obj) obj = SilKit::Config::Aggregation::Auto; else { - throw MakeConfigurationError("Unknown Aggregation"); + throw MakeError("Unknown Aggregation"); } } diff --git a/SilKit/source/config/YamlReader.hpp b/SilKit/source/config/YamlReader.hpp index 0af55f3e7..b5ce2b5a6 100644 --- a/SilKit/source/config/YamlReader.hpp +++ b/SilKit/source/config/YamlReader.hpp @@ -13,13 +13,13 @@ #include "rapidyaml.hpp" -#include "silkit_yaml/BasicYamlReader.hpp" +#include "SilKitYaml/BasicYamlReader.hpp" #include "config/ParticipantConfiguration.hpp" namespace VSilKit { -struct YamlReader : BasicYamlReader +struct YamlReader : SilKitYaml::BasicYamlReader { using BasicYamlReader::BasicYamlReader; using BasicYamlReader::Read; diff --git a/SilKit/source/config/YamlWriter.hpp b/SilKit/source/config/YamlWriter.hpp index dec13f500..a3786d6e7 100644 --- a/SilKit/source/config/YamlWriter.hpp +++ b/SilKit/source/config/YamlWriter.hpp @@ -12,13 +12,13 @@ #include "rapidyaml.hpp" -#include "silkit_yaml/BasicYamlWriter.hpp" +#include "SilKitYaml/BasicYamlWriter.hpp" #include "config/ParticipantConfiguration.hpp" namespace VSilKit { -struct YamlWriter : BasicYamlWriter +struct YamlWriter : SilKitYaml::BasicYamlWriter { using BasicYamlWriter::BasicYamlWriter; using BasicYamlWriter::OptionalWrite; diff --git a/SilKitYaml/CMakeLists.txt b/SilKitYaml/CMakeLists.txt new file mode 100644 index 000000000..33a07012e --- /dev/null +++ b/SilKitYaml/CMakeLists.txt @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: 2026 Vector Informatik GmbH +# +# SPDX-License-Identifier: MIT + +# SilKitYaml -- self-contained, header-only YAML (de)serialization on top of +# rapidyaml. Used internally by SIL Kit and vendorable into other projects via +# subrepo/submodule (add_subdirectory). It is NOT installed or exported, and its +# symbols are not leaked into the SIL Kit shared library. +# +# Requirement: the host project must provide a `rapidyaml` target. + +if(NOT TARGET rapidyaml) + message(FATAL_ERROR + "SilKitYaml requires a 'rapidyaml' target. Provide one before add_subdirectory(SilKitYaml).") +endif() + +add_library(SilKitYaml INTERFACE) +add_library(SilKitYaml::SilKitYaml ALIAS SilKitYaml) + +target_include_directories(SilKitYaml + INTERFACE + $ +) + +# rapidyaml carries its include dirs and RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS=1. +target_link_libraries(SilKitYaml INTERFACE rapidyaml) +target_compile_features(SilKitYaml INTERFACE cxx_std_17) + +# Convenience project for IDEs to surface the headers. +file(GLOB_RECURSE SILKIT_YAML_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp) +add_custom_target(SilKitYamlApi SOURCES ${SILKIT_YAML_HEADERS}) diff --git a/SilKitYaml/README.md b/SilKitYaml/README.md new file mode 100644 index 000000000..8f83fabcc --- /dev/null +++ b/SilKitYaml/README.md @@ -0,0 +1,73 @@ + + +# SilKitYaml — self-contained YAML (de)serialization + +`SilKitYaml` is a small, **header-only** C++17 library wrapping +[rapidyaml](https://github.com/biojppm/rapidyaml) with ergonomic CRTP-based +reader/writer bases and `Deserialize`/`Serialize` helpers. It lets you parse and +emit your own YAML/JSON formats against your own schemata. + +It is used **internally by SIL Kit** and is intentionally **self-contained**. + +## Properties + +- Namespace `SilKitYaml`. Errors are reported as `SilKitYaml::YamlError`. +- Depends on **nothing** except a `rapidyaml` target (it does **not** depend on + SIL Kit). The host project must provide a `rapidyaml` target. +- **Not installed and not exported** by SIL Kit; it is an internal interface + library whose symbols do not leak into the SIL Kit shared library. + +## CMake usage + +The host project provides a `rapidyaml` target, then: + +```cmake +add_subdirectory(SilKitYaml) +target_link_libraries(my_target PRIVATE SilKitYaml::SilKitYaml) +``` + +## Code + +```cpp +#include "SilKitYaml/BasicYamlReader.hpp" +#include "SilKitYaml/BasicYamlWriter.hpp" +#include "SilKitYaml/YamlSerdes.hpp" + +struct MyConfig +{ + std::string name; + int count{}; +}; + +struct MyReader : SilKitYaml::BasicYamlReader +{ + using BasicYamlReader::BasicYamlReader; + using BasicYamlReader::Read; + + void Read(MyConfig& c) + { + ReadKeyValue(c.name, "Name"); + OptionalRead(c.count, "Count"); + } +}; + +struct MyWriter : SilKitYaml::BasicYamlWriter +{ + using BasicYamlWriter::BasicYamlWriter; + using BasicYamlWriter::Write; + + void Write(const MyConfig& c) + { + MakeMap(); + WriteKeyValue("Name", c.name); + WriteKeyValue("Count", c.count); + } +}; + +auto cfg = SilKitYaml::Deserialize("Name: foo\nCount: 7\n"); +auto yaml = SilKitYaml::Serialize(cfg); +``` diff --git a/SilKit/include/silkit_yaml/BasicYamlReader.hpp b/SilKitYaml/include/SilKitYaml/BasicYamlReader.hpp similarity index 89% rename from SilKit/include/silkit_yaml/BasicYamlReader.hpp rename to SilKitYaml/include/SilKitYaml/BasicYamlReader.hpp index 64a392287..82b7fd557 100644 --- a/SilKit/include/silkit_yaml/BasicYamlReader.hpp +++ b/SilKitYaml/include/SilKitYaml/BasicYamlReader.hpp @@ -4,9 +4,7 @@ #pragma once -// UNSTABLE API: this auxiliary library does NOT carry the API/ABI stability -// guarantees of the main SIL Kit API (the silkit/ headers). Pin your SIL Kit -// version. See silkit_yaml/README for details. +// SilKitYaml: self-contained, header-only YAML deserialization. See README.md. #include #include @@ -18,13 +16,12 @@ #include #include -#include "silkit/participant/exception.hpp" - #include "rapidyaml.hpp" -#include "silkit_yaml/YamlParserUtils.hpp" +#include "SilKitYaml/YamlError.hpp" +#include "SilKitYaml/YamlParserUtils.hpp" -namespace VSilKit { +namespace SilKitYaml { template class BasicYamlReader @@ -107,7 +104,7 @@ class BasicYamlReader ss << " \"" << deprecatedFieldName << "\""; } ss << " are present."; - throw SilKit::ConfigurationError{ss.str()}; + throw YamlError{ss.str()}; } if (presentDeprecatedFieldNames.size() >= 2) @@ -119,7 +116,7 @@ class BasicYamlReader ss << " \"" << deprecatedFieldName << "\""; } ss << " are present."; - throw SilKit::ConfigurationError{ss.str()}; + throw YamlError{ss.str()}; } OptionalRead(value, fieldName); @@ -139,7 +136,7 @@ class BasicYamlReader { std::ostringstream s; s << "missing key: " << name; - throw MakeConfigurationError(s.str()); + throw MakeError(s.str()); } child.Read(value); @@ -166,7 +163,7 @@ class BasicYamlReader { if (!IsSequence()) { - throw MakeConfigurationError("Expected a sequence."); + throw MakeError("Expected a sequence."); } for (auto&& i : _node.cchildren()) @@ -227,10 +224,10 @@ class BasicYamlReader return node.is_map() && !node.find_child(ryml::to_csubstr(name)).invalid(); } - auto MakeConfigurationError(const std::string_view message) const -> SilKit::ConfigurationError + auto MakeError(const std::string_view message) const -> YamlError { const auto location = _parser.location(_node); - return VSilKit::MakeConfigurationError(location, message); + return SilKitYaml::MakeError(location, message); } auto GetChildSafe(const std::string& name) const -> Impl @@ -271,4 +268,4 @@ class BasicYamlReader } }; -} // namespace VSilKit +} // namespace SilKitYaml diff --git a/SilKit/include/silkit_yaml/BasicYamlWriter.hpp b/SilKitYaml/include/SilKitYaml/BasicYamlWriter.hpp similarity index 78% rename from SilKit/include/silkit_yaml/BasicYamlWriter.hpp rename to SilKitYaml/include/SilKitYaml/BasicYamlWriter.hpp index 91f3bf84f..1bddaa43e 100644 --- a/SilKit/include/silkit_yaml/BasicYamlWriter.hpp +++ b/SilKitYaml/include/SilKitYaml/BasicYamlWriter.hpp @@ -4,9 +4,7 @@ #pragma once -// UNSTABLE API: this auxiliary library does NOT carry the API/ABI stability -// guarantees of the main SIL Kit API (the silkit/ headers). Pin your SIL Kit -// version. See silkit_yaml/README for details. +// SilKitYaml: self-contained, header-only YAML serialization. See README.md. #include #include @@ -14,11 +12,11 @@ #include #include -#include "silkit/participant/exception.hpp" - #include "rapidyaml.hpp" -namespace VSilKit { +#include "SilKitYaml/YamlError.hpp" + +namespace SilKitYaml { template struct BasicYamlWriter @@ -72,7 +70,7 @@ struct BasicYamlWriter { if (!node.is_map()) { - throw SilKit::ConfigurationError("Parse error: trying to access child of something not a map"); + throw YamlError("Parse error: trying to access child of something not a map"); } auto writer = MakeImpl(node.append_child() << ryml::key(name)); @@ -102,13 +100,13 @@ struct BasicYamlWriter node |= ryml::MAP; } - auto MakeConfigurationError(const char* message) const -> SilKit::ConfigurationError + auto MakeError(const char* message) const -> YamlError { std::ostringstream s; - s << "error writing configuration: " << message; + s << "error writing yaml: " << message; - return SilKit::ConfigurationError{s.str()}; + return YamlError{s.str()}; } protected: @@ -129,4 +127,4 @@ struct BasicYamlWriter } }; -} // namespace VSilKit +} // namespace SilKitYaml diff --git a/SilKitYaml/include/SilKitYaml/YamlError.hpp b/SilKitYaml/include/SilKitYaml/YamlError.hpp new file mode 100644 index 000000000..6e8d99f6c --- /dev/null +++ b/SilKitYaml/include/SilKitYaml/YamlError.hpp @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2026 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include + +namespace SilKitYaml { + +//! \brief Exception thrown by SilKitYaml on YAML parse/serialize errors. +//! +//! SilKitYaml is self-contained and depends on nothing from SIL Kit; it reports +//! all errors via this type. Hosting projects that wrap SilKitYaml (such as SIL +//! Kit itself) may translate this into their own exception type at their boundary. +class YamlError : public std::exception +{ + std::string _what; + +public: + explicit YamlError(std::string message) + : _what{std::move(message)} + { + } + + explicit YamlError(const char* message) + : _what{message} + { + } + + const char* what() const noexcept override + { + return _what.c_str(); + } +}; + +} // namespace SilKitYaml diff --git a/SilKit/include/silkit_yaml/YamlParserUtils.hpp b/SilKitYaml/include/SilKitYaml/YamlParserUtils.hpp similarity index 54% rename from SilKit/include/silkit_yaml/YamlParserUtils.hpp rename to SilKitYaml/include/SilKitYaml/YamlParserUtils.hpp index c6f65d0eb..4100a3589 100644 --- a/SilKit/include/silkit_yaml/YamlParserUtils.hpp +++ b/SilKitYaml/include/SilKitYaml/YamlParserUtils.hpp @@ -4,30 +4,28 @@ #pragma once -// UNSTABLE API: this auxiliary library does NOT carry the API/ABI stability -// guarantees of the main SIL Kit API (the silkit/ headers). It is provided to let -// out-of-tree projects parse their own YAML formats on their own schemata by -// reusing SIL Kit's thin rapidyaml wrapper. Pin your SIL Kit version. -// See silkit_yaml/README for details. +// SilKitYaml: a self-contained, header-only YAML (de)serialization layer built on +// top of rapidyaml. It is consumed internally by SIL Kit and may be vendored into +// other projects via subrepo/submodule (add_subdirectory). It depends only on a +// `rapidyaml` target. See SilKitYaml/README.md. #include #include #include #include -#include "silkit/participant/exception.hpp" - #include "rapidyaml.hpp" -namespace VSilKit { +#include "SilKitYaml/YamlError.hpp" + +namespace SilKitYaml { -// Format a rapidyaml parse location and message into a SilKit::ConfigurationError. -inline auto MakeConfigurationError(ryml::Location location, - const std::string_view message) -> SilKit::ConfigurationError +// Format a rapidyaml parse location and message into a SilKitYaml::YamlError. +inline auto MakeError(ryml::Location location, const std::string_view message) -> YamlError { std::ostringstream s; - s << "error parsing configuration"; + s << "error parsing yaml"; if (location.name.empty()) { s << " string: "; @@ -39,7 +37,7 @@ inline auto MakeConfigurationError(ryml::Location location, s << "line " << (location.line + 1) << " column " << location.col << ": " << message; - return SilKit::ConfigurationError{s.str()}; + return YamlError{s.str()}; } namespace detail { @@ -57,17 +55,17 @@ inline void RapidyamlFree(void* ptr, size_t /*length*/, void* /*userData*/) inline void RapidyamlError(const char* message, const size_t length, ryml::Location location, void* /*userData*/) { const std::string_view rapidyamlMessage{message, length}; - throw VSilKit::MakeConfigurationError(location, rapidyamlMessage); + throw SilKitYaml::MakeError(location, rapidyamlMessage); } } // namespace detail // Error-handling callbacks for a ryml::Tree/Parser. The error callback nicely -// formats the location and throws SilKit::ConfigurationError. Requires the -// compile definition RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS=1. +// formats the location and throws SilKitYaml::YamlError. Requires the compile +// definition RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS=1 (provided by the rapidyaml target). inline auto GetRapidyamlCallbacks() -> ryml::Callbacks { return ryml::Callbacks{nullptr, detail::RapidyamlAllocate, detail::RapidyamlFree, detail::RapidyamlError}; } -} // namespace VSilKit +} // namespace SilKitYaml diff --git a/SilKit/include/silkit_yaml/YamlSerdes.hpp b/SilKitYaml/include/SilKitYaml/YamlSerdes.hpp similarity index 75% rename from SilKit/include/silkit_yaml/YamlSerdes.hpp rename to SilKitYaml/include/SilKitYaml/YamlSerdes.hpp index 1a1cccf3e..2edf837e9 100644 --- a/SilKit/include/silkit_yaml/YamlSerdes.hpp +++ b/SilKitYaml/include/SilKitYaml/YamlSerdes.hpp @@ -4,27 +4,26 @@ #pragma once -// UNSTABLE API: this auxiliary library does NOT carry the API/ABI stability -// guarantees of the main SIL Kit API (the silkit/ headers). Pin your SIL Kit -// version. See silkit_yaml/README for details. +// SilKitYaml: self-contained, header-only YAML/JSON (de)serialization. See README.md. #include -#include "silkit/participant/exception.hpp" - #include "rapidyaml.hpp" -#include "silkit_yaml/YamlParserUtils.hpp" +#include "SilKitYaml/YamlError.hpp" +#include "SilKitYaml/YamlParserUtils.hpp" -namespace VSilKit { +namespace SilKitYaml { ////////////////////////////////////////////////////////////////////// // Generic YAML/JSON (de)serialization // -// R must derive from VSilKit::BasicYamlReader and provide a +// R must derive from SilKitYaml::BasicYamlReader and provide a // void Read(T&) overload for the target type T. -// W must derive from VSilKit::BasicYamlWriter and provide a +// W must derive from SilKitYaml::BasicYamlWriter and provide a // void Write(const T&) overload for the source type T. +// +// All errors are reported as SilKitYaml::YamlError. ////////////////////////////////////////////////////////////////////// template @@ -35,7 +34,7 @@ auto Deserialize(const std::string& input) -> T return {}; } - const auto rapidyamlCallbacks = VSilKit::GetRapidyamlCallbacks(); + const auto rapidyamlCallbacks = SilKitYaml::GetRapidyamlCallbacks(); ryml::ParserOptions options{}; options.locations(true); @@ -61,7 +60,7 @@ auto Deserialize(const std::string& input) -> T } catch (const std::exception& ex) { - throw SilKit::ConfigurationError{ex.what()}; + throw YamlError{ex.what()}; } catch (...) { @@ -87,4 +86,4 @@ auto SerializeAsJson(const T& input) -> std::string return ryml::emitrs_json(t); } -} // namespace VSilKit +} // namespace SilKitYaml From 38e5aea4530338faf16cd8f636ac4e1fbc5d75ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20B=C3=B6rschig?= Date: Wed, 1 Jul 2026 10:11:52 +0200 Subject: [PATCH 3/7] Update SilKitYaml/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Edwards Signed-off-by: Marius Börschig --- SilKitYaml/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SilKitYaml/README.md b/SilKitYaml/README.md index 8f83fabcc..804dc966c 100644 --- a/SilKitYaml/README.md +++ b/SilKitYaml/README.md @@ -7,9 +7,9 @@ SPDX-License-Identifier: MIT # SilKitYaml — self-contained YAML (de)serialization `SilKitYaml` is a small, **header-only** C++17 library wrapping -[rapidyaml](https://github.com/biojppm/rapidyaml) with ergonomic CRTP-based -reader/writer bases and `Deserialize`/`Serialize` helpers. It lets you parse and -emit your own YAML/JSON formats against your own schemata. +[rapidyaml](https://github.com/biojppm/rapidyaml) with CRTP-based reader/writer +base classes and `Deserialize`/`Serialize` helper functions. It lets you parse +and emit your own YAML/JSON formats against your own schemata. It is used **internally by SIL Kit** and is intentionally **self-contained**. From 9cf0135a7e1ec1ffc2ceea179d0898cf25e27047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20B=C3=B6rschig?= Date: Wed, 1 Jul 2026 10:12:00 +0200 Subject: [PATCH 4/7] Update SilKitYaml/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Edwards Signed-off-by: Marius Börschig --- SilKitYaml/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SilKitYaml/README.md b/SilKitYaml/README.md index 804dc966c..0c87ea003 100644 --- a/SilKitYaml/README.md +++ b/SilKitYaml/README.md @@ -16,8 +16,8 @@ It is used **internally by SIL Kit** and is intentionally **self-contained**. ## Properties - Namespace `SilKitYaml`. Errors are reported as `SilKitYaml::YamlError`. -- Depends on **nothing** except a `rapidyaml` target (it does **not** depend on - SIL Kit). The host project must provide a `rapidyaml` target. +- Depends only on `rapidyaml`. It does **not** depend on SIL Kit. The host + project must provide a `rapidyaml` target. - **Not installed and not exported** by SIL Kit; it is an internal interface library whose symbols do not leak into the SIL Kit shared library. From efbcdaa5bfdf591e7060c37942d160564c69178e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20B=C3=B6rschig?= Date: Wed, 1 Jul 2026 11:46:25 +0200 Subject: [PATCH 5/7] fixup! fixup! config: factor yaml parsing out into a separate silkit_yaml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dont use rapidyaml in capability parser Signed-off-by: Marius Börschig --- SilKit/source/config/CapabilitiesParser.cpp | 52 ++++++++++++++------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/SilKit/source/config/CapabilitiesParser.cpp b/SilKit/source/config/CapabilitiesParser.cpp index f39779fc3..a885ef3b1 100644 --- a/SilKit/source/config/CapabilitiesParser.cpp +++ b/SilKit/source/config/CapabilitiesParser.cpp @@ -6,33 +6,51 @@ #include "silkit/participant/exception.hpp" -#include "rapidyaml.hpp" +#include "SilKitYaml/BasicYamlReader.hpp" +#include "SilKitYaml/YamlSerdes.hpp" namespace VSilKit { -auto ParseCapabilities(const std::string& input) -> std::vector> -{ - std::vector> result; - auto&& cinput = ryml::to_csubstr(input); - auto t = ryml::parse_in_arena(cinput); +using ValueT = std::vector>; - auto root = t.crootref(); - if (!root.is_seq()) - { - throw SilKit::ConfigurationError{"First element in Capabilities string is not a sequence"}; - } - if (root.has_children()) +namespace { +struct CapabilityReader: SilKitYaml::BasicYamlReader +{ + using BasicYamlReader::BasicYamlReader; + void Read(ValueT& value) { - for (auto&& child : root.children()) + if(!IsSequence()) { - if (!child.is_map()) + throw SilKitYaml::YamlError{"First element in Capabilities string is not a sequence"}; + } + + if(_node.has_children()) + { + for(auto&& i: _node.cchildren()) { - throw SilKit::ConfigurationError{"Capabilities should be a sequence of map objects."}; + auto&& parser = MakeImpl(i); + if(!parser.IsMap()) + { + throw SilKitYaml::YamlError{"Capabilities should be a sequence of map objects."}; + } + std::map element; + static_cast&>(parser).Read(element); + value.emplace_back(std::move(element)); } } } - root >> result; - return result; +}; + +} // end namespace +auto ParseCapabilities(const std::string& input) -> std::vector> +{ + try { + ValueT result; + return SilKitYaml::Deserialize(input); + } catch(const SilKitYaml::YamlError& ex) + { + throw SilKit::ConfigurationError{ex.what()}; + } } } // namespace VSilKit From 8cb1c35871c63662e43ba2b2698b0cda0c0b2f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20B=C3=B6rschig?= Date: Wed, 1 Jul 2026 11:50:24 +0200 Subject: [PATCH 6/7] fixup! fixup! fixup! config: factor yaml parsing out into a separate silkit_yaml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marius Börschig --- SilKit/source/config/CapabilitiesParser.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/SilKit/source/config/CapabilitiesParser.cpp b/SilKit/source/config/CapabilitiesParser.cpp index a885ef3b1..33ff1293c 100644 --- a/SilKit/source/config/CapabilitiesParser.cpp +++ b/SilKit/source/config/CapabilitiesParser.cpp @@ -45,7 +45,6 @@ struct CapabilityReader: SilKitYaml::BasicYamlReader auto ParseCapabilities(const std::string& input) -> std::vector> { try { - ValueT result; return SilKitYaml::Deserialize(input); } catch(const SilKitYaml::YamlError& ex) { From fb64dbb727749156974ba483b9e316240d5c41c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20B=C3=B6rschig?= Date: Thu, 2 Jul 2026 07:57:03 +0200 Subject: [PATCH 7/7] fixup! fixup! fixup! fixup! config: factor yaml parsing out into a separate silkit_yaml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marius Börschig --- CMakeLists.txt | 4 ++++ SilKitYaml/CMakeLists.txt | 30 +++++++++++++++++++++++++++--- SilKitYaml/README.md | 17 +++++++++++++---- 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b22f4969..95ae5c876 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,10 @@ cmake_minimum_required(VERSION 3.13) project("SilKit") +# Root of the SIL Kit tree; used by components (e.g. SilKitYaml) to locate +# vendored sources independently of the current subdirectory. +set(SILKIT_TOP_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + option(SILKIT_BUILD_DEMOS "Build the SIL Kit Demos" ON) option(SILKIT_BUILD_STATIC "Compile the SIL Kit as a static library" OFF) option(SILKIT_BUILD_TESTS "Enable unit and integration tests for the SIL Kit" ON) diff --git a/SilKitYaml/CMakeLists.txt b/SilKitYaml/CMakeLists.txt index 33a07012e..7dc1a221b 100644 --- a/SilKitYaml/CMakeLists.txt +++ b/SilKitYaml/CMakeLists.txt @@ -7,11 +7,35 @@ # subrepo/submodule (add_subdirectory). It is NOT installed or exported, and its # symbols are not leaked into the SIL Kit shared library. # -# Requirement: the host project must provide a `rapidyaml` target. +# rapidyaml dependency: if the host project already provides a `rapidyaml` target +# (as SIL Kit does in-tree), it is used as-is. Otherwise SilKitYaml provisions the +# vendored rapidyaml itself, locating it via SILKIT_TOP_DIR (the root of the SIL +# Kit tree). Both SILKIT_TOP_DIR and the concrete SILKITYAML_RAPIDYAML_DIR are +# overridable so hosts with a different layout, or their own rapidyaml, still work. + +# Root of the SIL Kit tree. Honor an outer definition (set by the top-level +# CMakeLists in-tree); otherwise self-locate: SilKitYaml lives at /SilKitYaml. +if(NOT DEFINED SILKIT_TOP_DIR) + get_filename_component(SILKIT_TOP_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) +endif() + +set(SILKITYAML_RAPIDYAML_DIR "${SILKIT_TOP_DIR}/ThirdParty/rapidyaml" + CACHE PATH "Location of the rapidyaml sources consumed by SilKitYaml") if(NOT TARGET rapidyaml) - message(FATAL_ERROR - "SilKitYaml requires a 'rapidyaml' target. Provide one before add_subdirectory(SilKitYaml).") + if(EXISTS "${SILKITYAML_RAPIDYAML_DIR}/CMakeLists.txt") + add_subdirectory("${SILKITYAML_RAPIDYAML_DIR}" + "${CMAKE_CURRENT_BINARY_DIR}/_rapidyaml") + # The rapidyaml folder only defines `rapidyaml-static`; provide the alias. + if(NOT TARGET rapidyaml) + add_library(rapidyaml ALIAS rapidyaml-static) + endif() + else() + message(FATAL_ERROR + "SilKitYaml: no 'rapidyaml' target and none found at " + "'${SILKITYAML_RAPIDYAML_DIR}'. Provide a 'rapidyaml' target, or set " + "SILKIT_TOP_DIR / SILKITYAML_RAPIDYAML_DIR to the rapidyaml location.") + endif() endif() add_library(SilKitYaml INTERFACE) diff --git a/SilKitYaml/README.md b/SilKitYaml/README.md index 0c87ea003..a7366103e 100644 --- a/SilKitYaml/README.md +++ b/SilKitYaml/README.md @@ -16,20 +16,29 @@ It is used **internally by SIL Kit** and is intentionally **self-contained**. ## Properties - Namespace `SilKitYaml`. Errors are reported as `SilKitYaml::YamlError`. -- Depends only on `rapidyaml`. It does **not** depend on SIL Kit. The host - project must provide a `rapidyaml` target. +- Depends only on `rapidyaml`. It does **not** depend on SIL Kit. - **Not installed and not exported** by SIL Kit; it is an internal interface library whose symbols do not leak into the SIL Kit shared library. ## CMake usage -The host project provides a `rapidyaml` target, then: - ```cmake add_subdirectory(SilKitYaml) target_link_libraries(my_target PRIVATE SilKitYaml::SilKitYaml) ``` +The `rapidyaml` dependency is resolved as follows: + +- If the host already defines a `rapidyaml` target (as SIL Kit does in-tree), it + is used as-is. +- Otherwise SilKitYaml provisions the vendored rapidyaml itself, locating it at + `${SILKIT_TOP_DIR}/ThirdParty/rapidyaml`. `SILKIT_TOP_DIR` defaults to the SIL + Kit tree root (self-located as the parent of this folder). + +To override for a different layout, set `SILKIT_TOP_DIR` or the more specific +`SILKITYAML_RAPIDYAML_DIR`, or simply provide your own `rapidyaml` target before +`add_subdirectory(SilKitYaml)`. + ## Code ```cpp