From 7a4eafeeebf7c071adc03839765d06361834e631 Mon Sep 17 00:00:00 2001 From: Ananya2003Gupta Date: Thu, 14 Sep 2023 17:27:16 +0530 Subject: [PATCH] Added abstract types in default parameters and '-l' language argument --- .gitignore | 1 + cmake/podioMacros.cmake | 15 ++++-- python/podio/test_MemberParser.py | 1 + python/podio_class_generator.py | 56 ++++++++++++-------- python/templates/CMakeLists.txt | 2 + python/templates/Constructor.jl.jinja2 | 49 ----------------- python/templates/JuliaCollection.jl.jinja2 | 5 -- python/templates/ParentModule.jl.jinja2 | 13 ++++- python/templates/macros/abstracttypes.jinja2 | 17 ++++++ tests/CMakeLists.txt | 14 +++++ tests/unittest.jl | 4 +- 11 files changed, 95 insertions(+), 82 deletions(-) delete mode 100644 python/templates/Constructor.jl.jinja2 delete mode 100644 python/templates/JuliaCollection.jl.jinja2 create mode 100644 python/templates/macros/abstracttypes.jinja2 diff --git a/.gitignore b/.gitignore index 22f536c88..ea025973c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ install tests/src tests/datamodel tests/extension_model +tests/datamodeljulia # Python *pyc diff --git a/cmake/podioMacros.cmake b/cmake/podioMacros.cmake index cf8124d18..e419244b3 100644 --- a/cmake/podioMacros.cmake +++ b/cmake/podioMacros.cmake @@ -128,13 +128,15 @@ set_property(CACHE PODIO_USE_CLANG_FORMAT PROPERTY STRINGS AUTO ON OFF) # passed directly to podio_class_generator.py and validated there # Default is ROOT # SCHEMA_EVOLUTION OPTIONAL: The path to the yaml file declaring the necessary schema evolution -# ) +# LANG OPTIONAL: The programming language choice +# Default is cpp +# ) # # Note that the create_${datamodel} target will always be called, but if the YAML_FILE has not changed # this is essentially a no-op, and should not cause re-compilation. #--------------------------------------------------------------------------------------------------- function(PODIO_GENERATE_DATAMODEL datamodel YAML_FILE RETURN_HEADERS RETURN_SOURCES) - CMAKE_PARSE_ARGUMENTS(ARG "" "OLD_DESCRIPTION;OUTPUT_FOLDER;UPSTREAM_EDM;SCHEMA_EVOLUTION" "IO_BACKEND_HANDLERS" ${ARGN}) + CMAKE_PARSE_ARGUMENTS(ARG "" "OLD_DESCRIPTION;OUTPUT_FOLDER;UPSTREAM_EDM;SCHEMA_EVOLUTION" "IO_BACKEND_HANDLERS;LANG" ${ARGN}) IF(NOT ARG_OUTPUT_FOLDER) SET(ARG_OUTPUT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR}) ENDIF() @@ -153,6 +155,13 @@ function(PODIO_GENERATE_DATAMODEL datamodel YAML_FILE RETURN_HEADERS RETURN_SOUR SET(ARG_IO_BACKEND_HANDLERS "ROOT") ENDIF() + # Check if the LANG argument is specified and set the language accordingly. + IF(ARG_LANG) + SET(LANGUAGE_ARG "-l=${ARG_LANG}") + ELSE() + SET(LANGUAGE_ARG "-l=cpp") # Default to C++ + ENDIF() + SET(SCHEMA_EVOLUTION_ARG "") IF (ARG_SCHEMA_EVOLUTION) SET(SCHEMA_EVOLUTION_ARG "--evolution_file=${ARG_SCHEMA_EVOLUTION}") @@ -201,7 +210,7 @@ function(PODIO_GENERATE_DATAMODEL datamodel YAML_FILE RETURN_HEADERS RETURN_SOUR message(STATUS "Creating '${datamodel}' datamodel") # we need to boostrap the data model, so this has to be executed in the cmake run execute_process( - COMMAND ${Python_EXECUTABLE} ${podio_PYTHON_DIR}/podio_class_generator.py ${CLANG_FORMAT_ARG} ${OLD_DESCRIPTION_ARG} ${SCHEMA_EVOLUTION_ARG} ${UPSTREAM_EDM_ARG} ${YAML_FILE} ${ARG_OUTPUT_FOLDER} ${datamodel} ${ARG_IO_BACKEND_HANDLERS} + COMMAND ${Python_EXECUTABLE} ${podio_PYTHON_DIR}/podio_class_generator.py ${CLANG_FORMAT_ARG} ${OLD_DESCRIPTION_ARG} ${SCHEMA_EVOLUTION_ARG} ${UPSTREAM_EDM_ARG} ${YAML_FILE} ${ARG_OUTPUT_FOLDER} ${datamodel} ${ARG_IO_BACKEND_HANDLERS} ${LANGUAGE_ARG} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} RESULT_VARIABLE podio_generate_command_retval ) diff --git a/python/podio/test_MemberParser.py b/python/podio/test_MemberParser.py index 4d0ca534d..852221ac5 100644 --- a/python/podio/test_MemberParser.py +++ b/python/podio/test_MemberParser.py @@ -232,6 +232,7 @@ def test_parse_invalid(self): try: self.assertRaises(DefinitionError, parser.parse, inp) except AssertionError: + # pylint: disable-next=raise-missing-from raise AssertionError(f"'{inp}' should raise a DefinitionError from the MemberParser") def test_parse_valid_no_description(self): diff --git a/python/podio_class_generator.py b/python/podio_class_generator.py index f4287d88c..722fccb1e 100755 --- a/python/podio_class_generator.py +++ b/python/podio_class_generator.py @@ -78,11 +78,12 @@ class IncludeFrom(IntEnum): class ClassGenerator: """The entry point for reading a datamodel definition and generating the necessary source code from it.""" - def __init__(self, yamlfile, install_dir, package_name, io_handlers, verbose, dryrun, + def __init__(self, yamlfile, install_dir, package_name, io_handlers, proglang, verbose, dryrun, upstream_edm, old_description, evolution_file): self.install_dir = install_dir self.package_name = package_name self.io_handlers = io_handlers + self.proglang = proglang self.verbose = verbose self.dryrun = dryrun self.yamlfile = yamlfile @@ -130,15 +131,18 @@ def process(self): for name, datatype in self.datamodel.datatypes.items(): datamodel['datatypes'].append(self._process_datatype(name, datatype)) - self._write_edm_def_file() - self._process_parent_module(datamodel) + if self.proglang == "julia": + self._process_parent_module(datamodel) - if 'ROOT' in self.io_handlers: - self._create_selection_xml() + if self.proglang == "cpp": + self._write_edm_def_file() - self._write_cmake_lists_file() - self.process_schema_evolution() + if 'ROOT' in self.io_handlers: + self._create_selection_xml() + + self._write_cmake_lists_file() + self.process_schema_evolution() self.print_report() def process_schema_evolution(self): @@ -264,23 +268,30 @@ def _process_component(self, name, component): component['includes_jl'] = {'struct': sorted(includes_jl)} component['class'] = DataType(name) - self._fill_templates('Component', component) - self._fill_templates('MutableStruct', component) + if self.proglang == "cpp": + self._fill_templates('Component', component) + if self.proglang == "julia": + self._fill_templates('MutableStruct', component) + return component def _process_datatype(self, name, definition): """Process one datatype""" datatype = self._preprocess_datatype(name, definition) - self._fill_templates('Data', datatype) - self._fill_templates('Object', datatype) - self._fill_templates('MutableObject', datatype) - self._fill_templates('Obj', datatype) - self._fill_templates('Collection', datatype) - self._fill_templates('CollectionData', datatype) - self._fill_templates('MutableStruct', datatype) - if 'SIO' in self.io_handlers: - self._fill_templates('SIOBlock', datatype) + if self.proglang == "julia": + self._fill_templates('MutableStruct', datatype) + + if self.proglang == "cpp": + self._fill_templates('Data', datatype) + self._fill_templates('Object', datatype) + self._fill_templates('MutableObject', datatype) + self._fill_templates('Obj', datatype) + self._fill_templates('Collection', datatype) + self._fill_templates('CollectionData', datatype) + + if 'SIO' in self.io_handlers: + self._fill_templates('SIOBlock', datatype) return datatype @@ -608,15 +619,18 @@ def read_upstream_edm(name_path): import argparse # pylint: disable=invalid-name # before 2.5.0 pylint is too strict with the naming here parser = argparse.ArgumentParser(description='Given a description yaml file this script generates ' - 'the necessary c++ files in the target directory') + 'the necessary c++ or julia files in the target directory') parser.add_argument('description', help='yaml file describing the datamodel') parser.add_argument('targetdir', help='Target directory where the generated data classes will be put. ' 'Header files will be put under //*.h. ' - 'Source files will be put under /src/*.cc') + 'Source files will be put under /src/*.cc. ' + 'Julia files will be put under //*.jl.') parser.add_argument('packagename', help='Name of the package.') parser.add_argument('iohandlers', choices=['ROOT', 'SIO'], nargs='+', help='The IO backend specific code that should be generated') + parser.add_argument('-l', '--lang', choices=['cpp', 'julia'], default='cpp', + help='Specify the programming language (default: cpp)') parser.add_argument('-q', '--quiet', dest='verbose', action='store_false', default=True, help='Don\'t write a report to screen') parser.add_argument('-d', '--dryrun', action='store_true', default=False, @@ -644,7 +658,7 @@ def read_upstream_edm(name_path): if not os.path.exists(directory): os.makedirs(directory) - gen = ClassGenerator(args.description, args.targetdir, args.packagename, args.iohandlers, + gen = ClassGenerator(args.description, args.targetdir, args.packagename, args.iohandlers, proglang=args.lang, verbose=args.verbose, dryrun=args.dryrun, upstream_edm=args.upstream_edm, old_description=args.old_description, evolution_file=args.evolution_file) if args.clangformat: diff --git a/python/templates/CMakeLists.txt b/python/templates/CMakeLists.txt index ba1a57d7e..6508484d9 100644 --- a/python/templates/CMakeLists.txt +++ b/python/templates/CMakeLists.txt @@ -24,4 +24,6 @@ set(PODIO_TEMPLATES ${CMAKE_CURRENT_LIST_DIR}/MutableStruct.jl.jinja2 ${CMAKE_CURRENT_LIST_DIR}/ParentModule.jl.jinja2 + ${CMAKE_CURRENT_LIST_DIR}/macros/abstracttypes.jinja2 + ${CMAKE_CURRENT_LIST_DIR}/macros/params.jinja2 ) diff --git a/python/templates/Constructor.jl.jinja2 b/python/templates/Constructor.jl.jinja2 deleted file mode 100644 index 4bac2b70c..000000000 --- a/python/templates/Constructor.jl.jinja2 +++ /dev/null @@ -1,49 +0,0 @@ -{% import "macros/params.jinja2" as params %} -include("{{ class.bare_type }}Struct.jl") -{% for include in includes_jl['constructor'] %} -{{ include }} -{% endfor %} - -module {{ class.bare_type }}Module -export {{ class.bare_type }} - -function {{ class.bare_type }}( - {% for member in Members %} - {% if member.is_array %} - {{ member.name }}::Main.{{ member.julia_type }} = Main.{{ member.julia_type }}(undef), - {% elif member.is_builtin %} - {{ member.name }}::{{ member.julia_type }} = {{ member.julia_type }}(0), - {% else %} - {{ member.name }}::Main.{{ member.julia_type }}Struct = Main.{{ member.julia_type }}(), - {% endif %} -{% endfor %} -{% for relation in OneToManyRelations %} - {{ relation.name }}::Vector{ Main.{{ relation.julia_type }}Struct } = Vector{ Main.{{ relation.julia_type }}Struct }(), -{% endfor %} -{% for relation in OneToOneRelations %} - {{ relation.name }}::Union{Nothing, Main.{{ relation.julia_type }}Struct } = nothing, -{% endfor %} -{% for member in VectorMembers %} - {% if member.is_builtin %} - {{ member.name }}::Vector{ {{ member.julia_type }} } = Vector{ {{ member.julia_type }} }([]), - {% else %} - {{ member.name }}::Vector{ Main.{{ member.julia_type }}Struct } = Vector{ Main.{{ member.julia_type }}Struct }([]), - {% endif %} -{% endfor %} -) - return Main.{{ class.bare_type }}Struct{{ params.julia_parameters(params_jl, "Main.", "Struct") }}( - {% for member in Members %} - {{ member.name }}, - {% endfor %} - {% for relation in OneToManyRelations %} - {{ relation.name }}, - {% endfor %} - {% for relation in OneToOneRelations %} - {{ relation.name }}, - {% endfor %} - {% for member in VectorMembers %} - {{ member.name }}, - {% endfor %} - ) -end -end \ No newline at end of file diff --git a/python/templates/JuliaCollection.jl.jinja2 b/python/templates/JuliaCollection.jl.jinja2 deleted file mode 100644 index a75d9252b..000000000 --- a/python/templates/JuliaCollection.jl.jinja2 +++ /dev/null @@ -1,5 +0,0 @@ -{% import "macros/params.jinja2" as params %} -include("{{ class.bare_type }}.jl") -using .{{ class.bare_type }}Module: {{class.bare_type}} - -{{ class.bare_type }}Collection = Vector{ Main.{{ class.bare_type }}Struct{{ params.julia_parameters(params_jl, prefix="Main.", suffix="Struct") }} } diff --git a/python/templates/ParentModule.jl.jinja2 b/python/templates/ParentModule.jl.jinja2 index 232a928e5..6a43e9633 100644 --- a/python/templates/ParentModule.jl.jinja2 +++ b/python/templates/ParentModule.jl.jinja2 @@ -1,4 +1,5 @@ {% import "macros/params.jinja2" as params %} +{% import "macros/abstracttypes.jinja2" as abstdatatype %} module {{ class.bare_type }} {% for component in components %} export {{ component['class'].bare_type }} @@ -21,7 +22,7 @@ function {{ component['class'].bare_type }}( {% if member.is_array %} {{ member.name }}::{{ member.julia_type }} = {{ member.julia_type }}(undef), {% elif member.is_builtin %} - {{ member.name }}::{{ member.julia_type }} = {{ member.julia_type }}(0), + {{ member.name }}::{{ abstdatatype.classify_data_type(member.julia_type) }} = {{ member.julia_type }}(0), {% else %} {{ member.name }}::{{ member.julia_type }}Struct = {{ member.julia_type }}(), {% endif %} @@ -42,7 +43,11 @@ function {{ component['class'].bare_type }}( ) return {{ component['class'].bare_type }}Struct{{ params.julia_parameters(component['params_jl'], "", "Struct") }}( {% for member in component['Members'] %} + {% if member.is_builtin %} + {{ member.julia_type }}({{ member.name }}), + {% else %} {{ member.name }}, + {% endif %} {% endfor %} {% for relation in component['OneToManyRelations'] %} {{ relation.name }}, @@ -64,7 +69,7 @@ function {{ datatype['class'].bare_type }}( {% if member.is_array %} {{ member.name }}::{{ member.julia_type }} = {{ member.julia_type }}(undef), {% elif member.is_builtin %} - {{ member.name }}::{{ member.julia_type }} = {{ member.julia_type }}(0), + {{ member.name }}::{{ abstdatatype.classify_data_type(member.julia_type) }} = {{ member.julia_type }}(0), {% else %} {{ member.name }}::{{ member.julia_type }}Struct = {{ member.julia_type }}(), {% endif %} @@ -85,7 +90,11 @@ function {{ datatype['class'].bare_type }}( ) return {{ datatype['class'].bare_type }}Struct{{ params.julia_parameters(datatype['params_jl'], "", "Struct") }}( {% for member in datatype['Members'] %} + {% if member.is_builtin %} + {{ member.julia_type }}({{ member.name }}), + {% else %} {{ member.name }}, + {% endif %} {% endfor %} {% for relation in datatype['OneToManyRelations'] %} {{ relation.name }}, diff --git a/python/templates/macros/abstracttypes.jinja2 b/python/templates/macros/abstracttypes.jinja2 new file mode 100644 index 000000000..a1e56cdba --- /dev/null +++ b/python/templates/macros/abstracttypes.jinja2 @@ -0,0 +1,17 @@ +{% macro classify_data_type(data_type) %} + {%- set type_map = { + 'Int8': 'Integer', + 'UInt8': 'Integer', + 'Int16': 'Integer', + 'UInt16': 'Integer', + 'Int32': 'Integer', + 'UInt32': 'Integer', + 'Int64': 'Integer', + 'UInt64': 'Integer', + 'Float16': 'Real', + 'Float32': 'Real', + 'Float64': 'Real' + } -%} + + {{ type_map.get(data_type, data_type) }} +{%- endmacro -%} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5fe0192f2..ae5590cc3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -65,6 +65,20 @@ add_subdirectory(dumpmodel) CREATE_PODIO_TEST(ostream_operator.cpp "") CREATE_PODIO_TEST(write_ascii.cpp "") +PODIO_GENERATE_DATAMODEL(datamodeljulia datalayout.yaml headers sources + IO_BACKEND_HANDLERS ${PODIO_IO_HANDLERS} + OLD_DESCRIPTION datalayout_old.yaml + SCHEMA_EVOLUTION schema_evolution.yaml + LANG julia + ) + +PODIO_GENERATE_DATAMODEL(extensionmodeljulia datalayout_extension.yaml ext_headers ext_sources + UPSTREAM_EDM datamodel:datalayout.yaml + IO_BACKEND_HANDLERS ${PODIO_IO_HANDLERS} + OUTPUT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR}/extension_model + LANG julia + ) + # Customize CTest to potentially disable some of the tests with known problems configure_file(CTestCustom.cmake ${PROJECT_BINARY_DIR}/CTestCustom.cmake @ONLY) find_program(Julia_EXECUTABLE julia) diff --git a/tests/unittest.jl b/tests/unittest.jl index 40f14ad76..cf47c5682 100644 --- a/tests/unittest.jl +++ b/tests/unittest.jl @@ -1,5 +1,5 @@ -include("datamodel/Datamodel.jl") -using .Datamodel +include("datamodeljulia/Datamodeljulia.jl") +using .Datamodeljulia using Test @testset "Julia Bindings" begin @testset "Relations" begin