Skip to content

Commit 0a5af60

Browse files
m-filatmadlener
andauthored
Add ExtraCode declarationFile and implementationFile directives (#601)
* add ExtraCode declarationFile and implementationFile directives * add declarationFile extracode for components * mend * fix pre-commit exclude * add test for mutable declarationFile and implementationFile * copy extra code files to root_io and sio_io for dumpmodel tests * revert embedding files with jinja * add including implementationFile and declarationFile during parsing * Update .pre-commit-config.yaml * remove checks for not yet implemented extra code, fix space Co-authored-by: tmadlener <[email protected]> * add documentation of file paths * add DEPENDS option to datamodel generating macro and use it with extra code files * Fix typo in documentation --------- Co-authored-by: tmadlener <[email protected]>
1 parent 6eefdc4 commit 0a5af60

12 files changed

+127
-30
lines changed

.pre-commit-config.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ repos:
1111
name: clang-tidy
1212
entry: clang-tidy -warnings-as-errors='*,-clang-diagnostic-deprecated-declarations' -p compile_commands.json
1313
types: [c++]
14-
exclude: (tests/(datamodel|src)/.*(h|cc)|podioVersion.in.h|SIOFrame.*h)
14+
exclude: (tests/(datamodel|src|extra_code)/.*(h|cc)|podioVersion.in.h|SIOFrame.*h)
1515
language: system
1616
- id: clang-format
1717
name: clang-format
1818
entry: .github/scripts/clang-format-hook
19-
exclude: (tests/(datamodel|src)/.*(h|cc)$|podioVersion.in.h)
19+
exclude: (tests/(datamodel|src|extra_code)/.*(h|cc)$|podioVersion.in.h)
2020
types: [c++]
2121
language: system
2222
- id: pylint

cmake/podioMacros.cmake

+3-1
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,14 @@ set_property(CACHE PODIO_USE_CLANG_FORMAT PROPERTY STRINGS AUTO ON OFF)
130130
# SCHEMA_EVOLUTION OPTIONAL: The path to the yaml file declaring the necessary schema evolution
131131
# LANG OPTIONAL: The programming language choice
132132
# Default is cpp
133+
# DEPENDS OPTIONAL: List of files to be added as configure dependencies of the datamodel
133134
# )
134135
#
135136
# Note that the create_${datamodel} target will always be called, but if the YAML_FILE has not changed
136137
# this is essentially a no-op, and should not cause re-compilation.
137138
#---------------------------------------------------------------------------------------------------
138139
function(PODIO_GENERATE_DATAMODEL datamodel YAML_FILE RETURN_HEADERS RETURN_SOURCES)
139-
CMAKE_PARSE_ARGUMENTS(ARG "" "OLD_DESCRIPTION;OUTPUT_FOLDER;UPSTREAM_EDM;SCHEMA_EVOLUTION" "IO_BACKEND_HANDLERS;LANG" ${ARGN})
140+
CMAKE_PARSE_ARGUMENTS(ARG "" "OLD_DESCRIPTION;OUTPUT_FOLDER;UPSTREAM_EDM;SCHEMA_EVOLUTION" "IO_BACKEND_HANDLERS;LANG;DEPENDS" ${ARGN})
140141
IF(NOT ARG_OUTPUT_FOLDER)
141142
SET(ARG_OUTPUT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR})
142143
ENDIF()
@@ -208,6 +209,7 @@ function(PODIO_GENERATE_DATAMODEL datamodel YAML_FILE RETURN_HEADERS RETURN_SOUR
208209
${podio_PYTHON_DIR}/podio_gen/generator_base.py
209210
${podio_PYTHON_DIR}/podio_gen/cpp_generator.py
210211
${podio_PYTHON_DIR}/podio_gen/julia_generator.py
212+
${ARG_DEPENDS}
211213
)
212214

213215
message(STATUS "Creating '${datamodel}' datamodel")

doc/datamodel_syntax.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -119,17 +119,17 @@ The `includes` will be add to the header files of the generated classes.
119119
includes: <newline separated list of strings (optional)>
120120
declaration: <string>
121121
implementation : <string>
122-
declarationFile: <string> (to be implemented!)
123-
implementationFile: <string> (to be implemented!)
122+
declarationFile: <string>
123+
implementationFile: <string>
124124
MutableExtraCode:
125125
includes: <newline separated list of strings (optional)>
126126
declaration: <string>
127127
implementation : <string>
128-
declarationFile: <string> (to be implemented!)
129-
implementationFile: <string> (to be implemented!)
128+
declarationFile: <string>
129+
implementationFile: <string>
130130
```
131131

132-
The code being provided has to use the macro `{name}` in place of the concrete name of the class.
132+
The code being provided has to use the macro `{name}` in place of the concrete name of the class. The paths to the files given in `declarationFile` and `implementationFile` should be either absolute or relative to the datamodel yaml file. The cmake users are encouraged to specify these file via the `DEPENDS` option of `PODIO_GENERATE_DATAMODEL` to add the files as configuration dependency of the datamodel and cause the datamodel re-generation when they change.
133133

134134
## Definition of custom interfaces
135135
An interface type can be defined as follows (again using an example from the example datamodel)

python/podio_gen/podio_config_reader.py

+40-20
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import copy
44
import re
5+
import os
56
import yaml
67

78
from podio_gen.generator_utils import (
@@ -171,9 +172,13 @@ class ClassDefinitionValidator:
171172
# it applies and also which accessor functions to generate
172173
required_interface_keys = required_datatype_keys + ("Members", "Types")
173174

174-
valid_extra_code_keys = ("declaration", "implementation", "includes")
175-
# documented but not yet implemented
176-
not_yet_implemented_extra_code = ("declarationFile", "implementationFile")
175+
valid_extra_code_keys = (
176+
"declaration",
177+
"implementation",
178+
"includes",
179+
"declarationFile",
180+
"implementationFile",
181+
)
177182

178183
@classmethod
179184
def validate(cls, datamodel, upstream_edm=None):
@@ -205,10 +210,10 @@ def _check_components(cls, datamodel, upstream_edm):
205210

206211
if "ExtraCode" in component:
207212
for key in component["ExtraCode"]:
208-
if key not in ("declaration", "includes"):
213+
if key not in ("declaration", "declarationFile", "includes"):
209214
raise DefinitionError(
210-
f"'{key}' field found in 'ExtraCode' of component '{name}'."
211-
" Only 'declaration' and 'includes' are allowed here"
215+
f"'{key}' field found in 'ExtraCode' of component '{name}'. "
216+
"Only 'declaration', 'declarationFile' and 'includes' are allowed here"
212217
)
213218

214219
for member in component["Members"]:
@@ -367,15 +372,8 @@ def _check_keys(cls, classname, definition):
367372
extracode = definition["ExtraCode"]
368373
invalid_keys = [k for k in extracode if k not in cls.valid_extra_code_keys]
369374
if invalid_keys:
370-
not_yet_impl = [k for k in invalid_keys if k in cls.not_yet_implemented_extra_code]
371-
if not_yet_impl:
372-
not_yet_impl = f" (not yet implemented: {not_yet_impl})"
373-
else:
374-
not_yet_impl = ""
375-
376375
raise DefinitionError(
377-
f"{classname} defines invalid 'ExtraCode' categories: "
378-
f"{invalid_keys}{not_yet_impl}"
376+
f"{classname} defines invalid 'ExtraCode' categories: " f"{invalid_keys}"
379377
)
380378

381379
@classmethod
@@ -442,8 +440,20 @@ def _handle_extracode(definition):
442440
"""Handle the extra code definition. Currently simply returning a copy"""
443441
return copy.deepcopy(definition)
444442

443+
@staticmethod
444+
def _expand_extracode(definitions, parent_path, directive):
445+
"""Expand extra code directives by including files from 'File' directives
446+
Relative paths to files are extended with parent_path. Mutates definitions"""
447+
if directive + "File" in definitions.keys():
448+
filename = definitions.pop(directive + "File")
449+
if not os.path.isabs(filename):
450+
filename = os.path.join(parent_path, filename)
451+
with open(filename, "r", encoding="utf-8") as stream:
452+
contents = stream.read()
453+
definitions[directive] = definitions.get(directive, "") + "\n" + contents
454+
445455
@classmethod
446-
def _read_component(cls, definition):
456+
def _read_component(cls, definition, parent_path):
447457
"""Read the component and put it into an easily digestible format."""
448458
component = {}
449459
for name, category in definition.items():
@@ -452,13 +462,17 @@ def _read_component(cls, definition):
452462
for member in definition[name]:
453463
# for components we do not require a description in the members
454464
component["Members"].append(cls.member_parser.parse(member, False))
465+
elif name == "ExtraCode":
466+
extra_code = copy.deepcopy(category)
467+
cls._expand_extracode(extra_code, parent_path, "declaration")
468+
component[name] = extra_code
455469
else:
456470
component[name] = copy.deepcopy(category)
457471

458472
return component
459473

460474
@classmethod
461-
def _read_datatype(cls, value):
475+
def _read_datatype(cls, value, parent_path):
462476
"""Read the datatype and put it into an easily digestible format"""
463477
datatype = {}
464478
for category, definition in value.items():
@@ -468,6 +482,11 @@ def _read_datatype(cls, value):
468482
for member in definition:
469483
members.append(cls.member_parser.parse(member))
470484
datatype[category] = members
485+
elif category in ("ExtraCode", "MutableExtraCode"):
486+
extra_code = copy.deepcopy(definition)
487+
cls._expand_extracode(extra_code, parent_path, "implementation")
488+
cls._expand_extracode(extra_code, parent_path, "declaration")
489+
datatype[category] = extra_code
471490
else:
472491
datatype[category] = copy.deepcopy(definition)
473492

@@ -494,7 +513,7 @@ def _read_interface(cls, value):
494513
return interface
495514

496515
@classmethod
497-
def parse_model(cls, model_dict, package_name, upstream_edm=None):
516+
def parse_model(cls, model_dict, package_name, upstream_edm=None, parent_path=None):
498517
"""Parse a model from the dictionary, e.g. read from a yaml file."""
499518

500519
try:
@@ -515,12 +534,12 @@ def parse_model(cls, model_dict, package_name, upstream_edm=None):
515534
components = {}
516535
if "components" in model_dict:
517536
for klassname, value in model_dict["components"].items():
518-
components[klassname] = cls._read_component(value)
537+
components[klassname] = cls._read_component(value, parent_path)
519538

520539
datatypes = {}
521540
if "datatypes" in model_dict:
522541
for klassname, value in model_dict["datatypes"].items():
523-
datatypes[klassname] = cls._read_datatype(value)
542+
datatypes[klassname] = cls._read_datatype(value, parent_path)
524543

525544
interfaces = {}
526545
if "interfaces" in model_dict:
@@ -549,5 +568,6 @@ def read(cls, yamlfile, package_name, upstream_edm=None):
549568
"""Read the datamodel definition from the yamlfile."""
550569
with open(yamlfile, "r", encoding="utf-8") as stream:
551570
content = yaml.load(stream, yaml.SafeLoader)
571+
parent_path = os.path.dirname(yamlfile)
552572

553-
return cls.parse_model(content, package_name, upstream_edm)
573+
return cls.parse_model(content, package_name, upstream_edm, parent_path)

tests/CMakeLists.txt

+10-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,17 @@ foreach( _conf ${CMAKE_CONFIGURATION_TYPES} )
55
set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${_conf} ${CMAKE_CURRENT_BINARY_DIR} )
66
endforeach()
77

8+
# files used in ExtraCode directives
9+
set(extra_code extra_code/component_declarations.cc
10+
extra_code/declarations.cc
11+
extra_code/implementations.cc
12+
extra_code/mutable_declarations.cc
13+
extra_code/mutable_implementations.cc
14+
)
15+
816
PODIO_GENERATE_DATAMODEL(datamodel datalayout.yaml headers sources
9-
IO_BACKEND_HANDLERS ${PODIO_IO_HANDLERS}
10-
)
17+
IO_BACKEND_HANDLERS ${PODIO_IO_HANDLERS} DEPENDS ${extra_code}
18+
)
1119

1220
# Use the cmake building blocks to add the different parts (conditionally)
1321
PODIO_ADD_DATAMODEL_CORE_LIB(TestDataModel "${headers}" "${sources}")

tests/datalayout.yaml

+29
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ components :
5353
- int i{42} // is there even another value to initialize ints to?
5454
- std::array<double, 10> arr {1.2, 3.4} // half initialized double array
5555

56+
StructWithExtraCode:
57+
Members:
58+
- int x
59+
ExtraCode:
60+
declaration: "
61+
int negate() { x = -x; return x; }\n
62+
"
63+
declarationFile: "extra_code/component_declarations.cc"
64+
5665
datatypes :
5766

5867
EventInfo:
@@ -209,6 +218,26 @@ datatypes :
209218
- TypeWithEnergy manyEnergies // multiple relations
210219
- ex42::AnotherTypeWithEnergy moreEnergies // multiple namespace relations
211220

221+
ExampleWithExternalExtraCode:
222+
Description: "Type showing usage of 'declaration', 'implementation', 'declarationFile' and 'implementationFile' directives'"
223+
Author: "Mateusz Jakub Fila"
224+
Members:
225+
- int number // a number
226+
ExtraCode:
227+
declaration:
228+
"int add(int i) const;"
229+
declarationFile: "extra_code/declarations.cc"
230+
implementation:
231+
"int {name}::add(int i) const { return number() + i; }"
232+
implementationFile: "extra_code/implementations.cc"
233+
MutableExtraCode:
234+
declaration:
235+
int add_inplace(int i);
236+
declarationFile: "extra_code/mutable_declarations.cc"
237+
implementation:
238+
int {name}::add_inplace(int i) { return number() += i; }
239+
implementationFile: "extra_code/mutable_implementations.cc"
240+
212241
nsp::EnergyInNamespace:
213242
Description: "A type with energy in a namespace"
214243
Author: "Thomas Madlener"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
int reset() {
2+
x = 0;
3+
return x;
4+
}

tests/extra_code/declarations.cc

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
bool gt(int i) const {
2+
return number() > i;
3+
}
4+
bool lt(int i) const;

tests/extra_code/implementations.cc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bool {name}::lt(int i) const { return number() < i; }
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int reset();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
int {name}::reset() {
2+
number() = 0;
3+
return number();
4+
}

tests/unittests/unittest.cpp

+24
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,17 @@
4343
#include "datamodel/ExampleWithArray.h"
4444
#include "datamodel/ExampleWithArrayComponent.h"
4545
#include "datamodel/ExampleWithComponent.h"
46+
#include "datamodel/ExampleWithExternalExtraCode.h"
4647
#include "datamodel/ExampleWithFixedWidthIntegers.h"
4748
#include "datamodel/ExampleWithOneRelationCollection.h"
4849
#include "datamodel/ExampleWithUserInitCollection.h"
4950
#include "datamodel/ExampleWithVectorMemberCollection.h"
5051
#include "datamodel/MutableExampleCluster.h"
5152
#include "datamodel/MutableExampleWithArray.h"
5253
#include "datamodel/MutableExampleWithComponent.h"
54+
#include "datamodel/MutableExampleWithExternalExtraCode.h"
55+
#include "datamodel/StructWithExtraCode.h"
56+
5357
#include "podio/UserDataCollection.h"
5458

5559
TEST_CASE("AutoDelete", "[basics][memory-management]") {
@@ -398,6 +402,26 @@ TEST_CASE("Extracode", "[basics][code-gen]") {
398402
REQUIRE(simple.z == 3);
399403
}
400404

405+
TEST_CASE("ExtraCode declarationFile and implementationFile", "[basics][code-gen]") {
406+
auto mutable_number = MutableExampleWithExternalExtraCode();
407+
REQUIRE(mutable_number.reset() == 0);
408+
REQUIRE(mutable_number.add(2) == 2);
409+
REQUIRE(mutable_number.add_inplace(1) == 1);
410+
REQUIRE(mutable_number.gt(-1));
411+
REQUIRE(mutable_number.lt(100));
412+
ExampleWithExternalExtraCode number = mutable_number;
413+
REQUIRE(number.add(1) == 2);
414+
REQUIRE(number.gt(-1));
415+
REQUIRE(number.lt(100));
416+
}
417+
418+
TEST_CASE("ExtraCode declarationFile in component", "[basics][code-gen]") {
419+
auto value = StructWithExtraCode();
420+
value.x = 1;
421+
REQUIRE(value.negate() == -1);
422+
REQUIRE(value.reset() == 0);
423+
}
424+
401425
TEST_CASE("AssociativeContainer", "[basics]") {
402426
auto clu1 = MutableExampleCluster();
403427
auto clu2 = MutableExampleCluster();

0 commit comments

Comments
 (0)