blob: 5d07b494d2e877f0a7835a8564afff52bae26754 [file] [log] [blame]
Deepak Kodihalli5de09572017-05-16 23:53:40 -05001## This file is a template. The comment below is emitted
2## into the rendered file; feel free to edit this file.
3// WARNING: Generated header. Do not edit!
Deepak Kodihalli5de09572017-05-16 23:53:40 -05004<%
Dhruvaraj Subhashchandran61d3b6a2017-07-25 09:36:54 -05005import re
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -05006from collections import defaultdict
Patrick Williamsbcf95782021-05-05 16:20:32 -05007from sdbusplus.namedelement import NamedElement
Patrick Williams2b7152f2020-04-02 07:18:32 -05008objects = settingsDict.keys()
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -05009sdbusplus_includes = []
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -050010props = defaultdict(list)
Dhruvaraj Subhashchandran61d3b6a2017-07-25 09:36:54 -050011validators = defaultdict(tuple)
Deepak Kodihalli5de09572017-05-16 23:53:40 -050012
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -050013def get_setting_sdbusplus_type(setting_intf):
Deepak Kodihalli5de09572017-05-16 23:53:40 -050014 setting = "sdbusplus::" + setting_intf.replace('.', '::')
15 i = setting.rfind('::')
16 setting = setting[:i] + '::server::' + setting[i+2:]
17 return setting
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -050018
Deepak Kodihallidb838622017-08-27 02:46:47 -050019def get_setting_type(path):
20 path = path[1:]
21 path = path.replace('/', '::')
22 return path
Matt Spinlerffdf8652023-05-24 11:09:54 -050023
24def get_default_value(object, interface, prop):
25 default_value = None
26 for item in settingsDict[object]:
27 if item['Interface'] == interface:
28 default_value = item['Properties'][prop]['Default']
29 break
30
31 if isinstance(default_value, str) and not \
32 default_value.startswith('"') and '::' in default_value:
33 ns = get_setting_sdbusplus_type(interface)
34 i = ns.rfind('::')
35 default_value = "{}::{}".format(ns[:i], default_value)
36
37 return default_value
Deepak Kodihalli5de09572017-05-16 23:53:40 -050038%>\
39#pragma once
40
41% for object in objects:
Deepak Kodihallidb838622017-08-27 02:46:47 -050042 % for item in settingsDict[object]:
Deepak Kodihalli5de09572017-05-16 23:53:40 -050043<%
Deepak Kodihallidb838622017-08-27 02:46:47 -050044 include = item['Interface']
Deepak Kodihalli5de09572017-05-16 23:53:40 -050045 include = include.replace('.', '/')
46 include = include + "/server.hpp"
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -050047 sdbusplus_includes.append(include)
Deepak Kodihalli5de09572017-05-16 23:53:40 -050048%>\
Deepak Kodihallidb838622017-08-27 02:46:47 -050049 % endfor
Deepak Kodihalli5de09572017-05-16 23:53:40 -050050% endfor
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -050051#include <cereal/archives/json.hpp>
James Feist74e3be72019-02-15 09:59:42 -080052#include <cereal/types/vector.hpp>
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -050053#include <fstream>
54#include <utility>
Patrick Williams6306e5e2022-06-16 17:14:54 -050055#include <filesystem>
Dhruvaraj Subhashchandran61d3b6a2017-07-25 09:36:54 -050056#include <regex>
57#include <phosphor-logging/elog.hpp>
58#include <phosphor-logging/elog-errors.hpp>
Matt Spinlerc2f84c72023-05-24 13:51:03 -050059#include <phosphor-logging/lg2.hpp>
Dhruvaraj Subhashchandran61d3b6a2017-07-25 09:36:54 -050060#include <xyz/openbmc_project/Common/error.hpp>
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -050061
Jagpal Singh Gill4d28bcd2023-04-23 23:34:05 -070062/* The DBus busname to own */
63#define SETTINGS_BUSNAME "xyz.openbmc_project.Settings"
64/* Path of directory housing persisted settings */
65#define SETTINGS_PERSIST_PATH "/var/lib/phosphor-settings-manager/settings"
66
67/* Class version to register with Cereal */
Matt Spinlerc2f84c72023-05-24 13:51:03 -050068static constexpr size_t CLASS_VERSION = 2;
69static constexpr size_t CLASS_VERSION_WITH_NVP = 2;
Jagpal Singh Gill4d28bcd2023-04-23 23:34:05 -070070
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -050071% for i in set(sdbusplus_includes):
Deepak Kodihalli5de09572017-05-16 23:53:40 -050072#include "${i}"
73% endfor
74
Deepak Kodihalli5de09572017-05-16 23:53:40 -050075namespace phosphor
76{
77namespace settings
78{
79
Patrick Williams6306e5e2022-06-16 17:14:54 -050080namespace fs = std::filesystem;
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -050081
Deepak Kodihalli242bc772017-08-04 02:47:54 -050082namespace persistent
83{
84
85// A setting d-bus object /foo/bar/baz is persisted in the filesystem with the
86// same path. This eases re-construction of settings objects when we restore
87// from the filesystem. This can be a problem though when you have two objects
88// such as - /foo/bar and /foo/bar/baz. This is because 'bar' will be treated as
89// a file in the first case, and a subdir in the second. To solve this, suffix
90// files with a trailing __. The __ is a safe character sequence to use, because
91// we won't have d-bus object paths ending with this.
92// With this the objects would be persisted as - /foo/bar__ and /foo/bar/baz__.
93constexpr auto fileSuffix = "__";
94
95}
96
Matt Spinlerfb1ad7c2023-05-24 14:28:29 -050097static fs::path getFilePath(const fs::path& objectPath)
98{
99 fs::path p(SETTINGS_PERSIST_PATH);
100 p /= objectPath.relative_path();
101 p += persistent::fileSuffix;
102 return p;
103}
104
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500105% for object in objects:
106<%
Deepak Kodihallidb838622017-08-27 02:46:47 -0500107 ns = object.split('/')
108 ns.pop(0)
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500109%>\
110% for n in ns:
111namespace ${n}
112{
113% endfor
Deepak Kodihallidb838622017-08-27 02:46:47 -0500114<%
115 interfaces = []
116 aliases = []
117 for item in settingsDict[object]:
118 interfaces.append(item['Interface'])
119 for name, meta in item['Properties'].items():
120 if 'Validation' in meta:
121 dict = meta['Validation']
122 if dict['Type'] == "range":
123 validators[name] = (dict['Type'], dict['Validator'], dict['Unit'])
124 else:
125 validators[name] = (dict['Type'], dict['Validator'])
126%>
127% for index, intf in enumerate(interfaces):
128using Iface${index} = ${get_setting_sdbusplus_type(intf)};
129<% aliases.append("Iface" + str(index)) %>\
130% endfor
131<%
Patrick Williams7c4181c2022-07-22 19:26:52 -0500132 parent = "sdbusplus::server::object_t" + "<" + ", ".join(aliases) + ">"
Deepak Kodihallidb838622017-08-27 02:46:47 -0500133%>\
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500134using Parent = ${parent};
135
136class Impl : public Parent
137{
138 public:
Patrick Williams7c4181c2022-07-22 19:26:52 -0500139 Impl(sdbusplus::bus_t& bus, const char* path):
Patrick Williams74c4f3b2022-04-05 16:16:20 -0500140 Parent(bus, path, Parent::action::defer_emit),
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500141 path(path)
142 {
143 }
144 virtual ~Impl() = default;
145
Matt Spinlerfb1ad7c2023-05-24 14:28:29 -0500146 void setInitialVersion(std::uint32_t v)
147 {
148 initialVersion = v;
149 }
150
151 std::uint32_t getInitialVersion() const
152 {
153 return initialVersion;
154 }
155
156 bool deserialize()
157 {
158 auto p = getFilePath(path);
159 if (fs::exists(p))
160 {
161 std::ifstream is(p.c_str(), std::ios::in);
162 cereal::JSONInputArchive iarchive(is);
163 iarchive(*this);
164 return true;
165 }
166 return false;
167 }
168
169 void serialize()
170 {
171 auto p = getFilePath(path);
172 if (!fs::exists(p.parent_path()))
173 {
174 fs::create_directories(p.parent_path());
175 }
176 std::ofstream os(p.c_str(), std::ios::binary);
177 cereal::JSONOutputArchive oarchive(os);
178 oarchive(*this);
179 }
180
181 void removeFile() const
182 {
183 std::error_code ec;
184 fs::remove(getFilePath(path), ec);
185 }
186
Deepak Kodihallidb838622017-08-27 02:46:47 -0500187% for index, item in enumerate(settingsDict[object]):
188 % for propName, metaDict in item['Properties'].items():
Patrick Williamsbcf95782021-05-05 16:20:32 -0500189<% t = NamedElement(name=propName).camelCase %>\
Deepak Kodihallidb838622017-08-27 02:46:47 -0500190<% fname = "validate" + propName %>\
191 decltype(std::declval<Iface${index}>().${t}()) ${t}(decltype(std::declval<Iface${index}>().${t}()) value) override
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500192 {
Deepak Kodihallidb838622017-08-27 02:46:47 -0500193 auto result = Iface${index}::${t}();
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500194 if (value != result)
195 {
Deepak Kodihallidb838622017-08-27 02:46:47 -0500196 % if propName in validators:
Dhruvaraj Subhashchandran61d3b6a2017-07-25 09:36:54 -0500197 if (!${fname}(value))
198 {
199 namespace error =
200 sdbusplus::xyz::openbmc_project::Common::Error;
201 namespace metadata =
202 phosphor::logging::xyz::openbmc_project::Common;
203 phosphor::logging::report<error::InvalidArgument>(
204 metadata::InvalidArgument::ARGUMENT_NAME("${t}"),
Deepak Kodihallidb838622017-08-27 02:46:47 -0500205 % if validators[propName][0] != "regex":
Dhruvaraj Subhashchandran61d3b6a2017-07-25 09:36:54 -0500206 metadata::InvalidArgument::ARGUMENT_VALUE(std::to_string(value).c_str()));
207 % else:
208 metadata::InvalidArgument::ARGUMENT_VALUE(value.c_str()));
209 % endif
210 return result;
211 }
212 % endif
Deepak Kodihallidb838622017-08-27 02:46:47 -0500213 result = Iface${index}::${t}(value);
Matt Spinlerfb1ad7c2023-05-24 14:28:29 -0500214 serialize();
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500215 }
216 return result;
217 }
Deepak Kodihallidb838622017-08-27 02:46:47 -0500218 using Iface${index}::${t};
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500219
Deepak Kodihallidb838622017-08-27 02:46:47 -0500220 % endfor
Andrew Geisslerc15990a2017-07-06 11:36:31 -0500221% endfor
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500222 private:
223 fs::path path;
Matt Spinlerfb1ad7c2023-05-24 14:28:29 -0500224 std::uint32_t initialVersion = 0;
Deepak Kodihallidb838622017-08-27 02:46:47 -0500225% for index, item in enumerate(settingsDict[object]):
226 % for propName, metaDict in item['Properties'].items():
Patrick Williamsbcf95782021-05-05 16:20:32 -0500227<% t = NamedElement(name=propName).camelCase %>\
Deepak Kodihallidb838622017-08-27 02:46:47 -0500228<% fname = "validate" + propName %>\
229 % if propName in validators:
Dhruvaraj Subhashchandran61d3b6a2017-07-25 09:36:54 -0500230
Deepak Kodihallidb838622017-08-27 02:46:47 -0500231 bool ${fname}(decltype(std::declval<Iface${index}>().${t}()) value)
Dhruvaraj Subhashchandran61d3b6a2017-07-25 09:36:54 -0500232 {
233 bool matched = false;
Deepak Kodihallidb838622017-08-27 02:46:47 -0500234 % if (validators[propName][0] == 'regex'):
235 std::regex regexToCheck("${validators[propName][1]}");
Dhruvaraj Subhashchandran61d3b6a2017-07-25 09:36:54 -0500236 matched = std::regex_search(value, regexToCheck);
237 if (!matched)
238 {
Matt Spinlere568fca2023-05-25 10:55:56 -0500239 lg2::error("Input parameter for ${propName} is invalid. "
240 "Input '{VALUE}' not in the format of this regex: "
241 "${validators[propName][1]}", "VALUE", value);
Dhruvaraj Subhashchandran61d3b6a2017-07-25 09:36:54 -0500242 }
Deepak Kodihallidb838622017-08-27 02:46:47 -0500243 % elif (validators[propName][0] == 'range'):
244<% lowhigh = re.split('\.\.', validators[propName][1]) %>\
Jagpal Singh Gillcfd49eb2023-04-23 23:09:09 -0700245 % if lowhigh[0] == '0':
246 if (value <= ${lowhigh[1]})
247 % else:
Dhruvaraj Subhashchandran61d3b6a2017-07-25 09:36:54 -0500248 if ((value <= ${lowhigh[1]}) && (value >= ${lowhigh[0]}))
Jagpal Singh Gillcfd49eb2023-04-23 23:09:09 -0700249 % endif
Dhruvaraj Subhashchandran61d3b6a2017-07-25 09:36:54 -0500250 {
251 matched = true;
252 }
253 else
254 {
Matt Spinlere568fca2023-05-25 10:55:56 -0500255 lg2::error("Input parameter for ${propName} is invalid. "
256 "Input '{VALUE}' with unit '${validators[propName][2]}' "
257 "is not in range ${validators[propName][1]}",
258 "VALUE", std::to_string(value));
Dhruvaraj Subhashchandran61d3b6a2017-07-25 09:36:54 -0500259 }
Deepak Kodihallidb838622017-08-27 02:46:47 -0500260 % else:
261 <% assert("Unknown validation type: propName") %>\
Dhruvaraj Subhashchandran61d3b6a2017-07-25 09:36:54 -0500262 % endif
263 return matched;
264 }
Deepak Kodihallidb838622017-08-27 02:46:47 -0500265 % endif
266 % endfor
Dhruvaraj Subhashchandran61d3b6a2017-07-25 09:36:54 -0500267% endfor
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500268};
269
270template<class Archive>
271void save(Archive& a,
Vishwanatha Subbannaa29a3eb2017-09-29 19:18:20 +0530272 const Impl& setting,
Jagpal Singh Gillcfd49eb2023-04-23 23:09:09 -0700273 [[maybe_unused]] const std::uint32_t version)
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500274{
275<%
Deepak Kodihallidb838622017-08-27 02:46:47 -0500276props = []
277for index, item in enumerate(settingsDict[object]):
Matt Spinlerc2f84c72023-05-24 13:51:03 -0500278 props.extend(item['Properties'].keys())
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500279%>\
Matt Spinlerc2f84c72023-05-24 13:51:03 -0500280## Since the iface isn't saved, property names need to be unique on
281## the object path. This could be supported by providing unique
282## field names to make_nvp() if ever necessary.
283% if len(set(props)) != len(props):
284#error Duplicate property names on object path ${object}
285%endif
286<%
287args = []
288for prop in props:
289 t = "setting." + NamedElement(name=prop).camelCase + "()"
290 args.append(f"cereal::make_nvp(\"{prop}\", {t})")
291args = ", ".join(args)
292%>\
293 a(${args});
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500294}
295
296template<class Archive>
297void load(Archive& a,
Vishwanatha Subbannaa29a3eb2017-09-29 19:18:20 +0530298 Impl& setting,
Matt Spinlerc2f84c72023-05-24 13:51:03 -0500299 const std::uint32_t version)
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500300{
Matt Spinlerfb1ad7c2023-05-24 14:28:29 -0500301 setting.setInitialVersion(version);
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500302<%
Matt Spinlerc2f84c72023-05-24 13:51:03 -0500303props = []
304for index, item in enumerate(settingsDict[object]):
305 for prop in item['Properties'].keys():
306 t = "setting." + NamedElement(name=prop).camelCase + "()"
307 props.append({'prop' : prop, 'iface': item['Interface'], 'func': t})
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500308%>\
Matt Spinlerc2f84c72023-05-24 13:51:03 -0500309% for p in props:
310 decltype(${p['func']}) ${p['prop']}{};
Deepak Kodihallidb838622017-08-27 02:46:47 -0500311% endfor
Matt Spinlerc2f84c72023-05-24 13:51:03 -0500312<% propList = ', '.join([p['prop'] for p in props]) %>
313% if propList:
314 if (version < CLASS_VERSION_WITH_NVP)
315 {
316 a(${propList});
317 }
318 else
319 {
320 % for p in props:
321 try
322 {
323 a(CEREAL_NVP(${p['prop']}));
324 }
325 catch (const cereal::Exception& e)
326 {
327 lg2::info("Could not restore property ${p['prop']} on ${object}, setting to default value");
328 ${p['prop']} = ${get_default_value(object, p['iface'], p['prop'])};
329 }
330 % endfor
331 }
Lei YUc0ce9922022-03-09 16:01:10 +0800332% endif
Deepak Kodihallidb838622017-08-27 02:46:47 -0500333<% props = [] %>
334% for index, item in enumerate(settingsDict[object]):
335 % for prop, metaDict in item['Properties'].items():
336<%
Patrick Williamsbcf95782021-05-05 16:20:32 -0500337 t = "setting." + NamedElement(name=prop).camelCase + "(" + prop + ")"
Deepak Kodihallidb838622017-08-27 02:46:47 -0500338%>\
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500339 ${t};
Deepak Kodihallidb838622017-08-27 02:46:47 -0500340 % endfor
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500341% endfor
342}
343
344% for n in reversed(ns):
345} // namespace ${n}
346% endfor
Deepak Kodihallidb838622017-08-27 02:46:47 -0500347
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500348% endfor
349
Deepak Kodihalli5de09572017-05-16 23:53:40 -0500350/** @class Manager
351 *
352 * @brief Compose settings objects and put them on the bus.
353 */
354class Manager
355{
356 public:
357 Manager() = delete;
358 Manager(const Manager&) = delete;
359 Manager& operator=(const Manager&) = delete;
360 Manager(Manager&&) = delete;
361 Manager& operator=(Manager&&) = delete;
362 virtual ~Manager() = default;
363
364 /** @brief Constructor to put settings objects on to the bus.
365 * @param[in] bus - Bus to attach to.
366 */
Patrick Williams7c4181c2022-07-22 19:26:52 -0500367 explicit Manager(sdbusplus::bus_t& bus) :
Patrick Williams0f6903d2022-04-15 10:03:58 -0500368 settings(
Deepak Kodihalli5de09572017-05-16 23:53:40 -0500369 std::make_tuple(
Deepak Kodihallidb838622017-08-27 02:46:47 -0500370% for index, path in enumerate(objects):
371<% type = get_setting_type(path) + "::Impl" %>\
Deepak Kodihalli5de09572017-05-16 23:53:40 -0500372 std::make_unique<${type}>(
373 bus,
374 % if index < len(settingsDict) - 1:
Deepak Kodihallidb838622017-08-27 02:46:47 -0500375 "${path}"),
Deepak Kodihalli5de09572017-05-16 23:53:40 -0500376 % else:
Patrick Williams0f6903d2022-04-15 10:03:58 -0500377 "${path}")
Deepak Kodihalli5de09572017-05-16 23:53:40 -0500378 % endif
379% endfor
Patrick Williams0f6903d2022-04-15 10:03:58 -0500380 )
381 )
382 {
Deepak Kodihallidb838622017-08-27 02:46:47 -0500383% for index, path in enumerate(objects):
Tom Joseph4636e072017-09-24 20:47:24 +0530384 auto initSetting${index} = [&]()
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500385 {
Deepak Kodihallidb838622017-08-27 02:46:47 -0500386 % for item in settingsDict[path]:
387 % for propName, metaDict in item['Properties'].items():
Patrick Williamsbcf95782021-05-05 16:20:32 -0500388<% p = NamedElement(name=propName).camelCase %>\
Matt Spinlerffdf8652023-05-24 11:09:54 -0500389<% defaultValue = get_default_value(path, item['Interface'], propName) %>\
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500390 std::get<${index}>(settings)->
Tom Joseph4636e072017-09-24 20:47:24 +0530391 ${get_setting_sdbusplus_type(item['Interface'])}::${p}(${defaultValue});
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500392 % endfor
Tom Joseph4636e072017-09-24 20:47:24 +0530393% endfor
394 };
395
396 try
397 {
Matt Spinlerfb1ad7c2023-05-24 14:28:29 -0500398 if (std::get<${index}>(settings)->deserialize())
Tom Joseph4636e072017-09-24 20:47:24 +0530399 {
Matt Spinlerfb1ad7c2023-05-24 14:28:29 -0500400 /* Update the archive to use name/value pairs if it isn't. */
401 if (std::get<${index}>(settings)->getInitialVersion() < CLASS_VERSION_WITH_NVP)
402 {
403 std::get<${index}>(settings)->serialize();
404 }
Tom Joseph4636e072017-09-24 20:47:24 +0530405 }
406 else
407 {
408 initSetting${index}();
409 }
410 }
Patrick Williamsb6fa9bb2021-10-06 12:27:57 -0500411 catch (const cereal::Exception& e)
Tom Joseph4636e072017-09-24 20:47:24 +0530412 {
Matt Spinlere568fca2023-05-25 10:55:56 -0500413 lg2::error("Cereal exception on ${path}: {ERROR}", "ERROR", e);
Matt Spinlerfb1ad7c2023-05-24 14:28:29 -0500414 std::get<${index}>(settings)->removeFile();
Tom Joseph4636e072017-09-24 20:47:24 +0530415 initSetting${index}();
Deepak Kodihallidb838622017-08-27 02:46:47 -0500416 }
Deepak Kodihalli7a6f2522017-06-23 23:05:47 -0500417 std::get<${index}>(settings)->emit_object_added();
Deepak Kodihalli5de09572017-05-16 23:53:40 -0500418
419% endfor
420 }
421
422 private:
423 /* @brief Composition of settings objects. */
424 std::tuple<
Deepak Kodihallidb838622017-08-27 02:46:47 -0500425% for index, path in enumerate(objects):
426<% type = get_setting_type(path) + "::Impl" %>\
Deepak Kodihalli5de09572017-05-16 23:53:40 -0500427 % if index < len(settingsDict) - 1:
428 std::unique_ptr<${type}>,
429 % else:
430 std::unique_ptr<${type}>> settings;
431 % endif
432% endfor
433};
434
435} // namespace settings
436} // namespace phosphor
Vishwanatha Subbannaa29a3eb2017-09-29 19:18:20 +0530437
438// Now register the class version with Cereal
439% for object in objects:
440<%
441 classname = "phosphor::settings"
442 ns = object.split('/')
443 ns.pop(0)
444%>\
445% for n in ns:
446<%
447 classname += "::" + n
448%>\
449% endfor
450CEREAL_CLASS_VERSION(${classname + "::Impl"}, CLASS_VERSION);
451% endfor