Skip to content

Commit

Permalink
Generate an additional cmake lists file containing the generated sour…
Browse files Browse the repository at this point in the history
…ce files (#143)

* Create cmake lists file with generated files

* Make templates and yaml file changes retrigger cmake

* Make changes to the code generator retrigger generation

* Make macro abort cmake if datamodel cannot be generated
  • Loading branch information
tmadlener authored Sep 21, 2021
1 parent c50af75 commit 760a9f5
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 28 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,6 @@ spack*
# Tooling
/.clangd/
/compile_commands.json

# Generated files
*podio_generated_files.cmake
32 changes: 21 additions & 11 deletions cmake/podioMacros.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -134,27 +134,37 @@ function(PODIO_GENERATE_DATAMODEL datamodel YAML_FILE RETURN_HEADERS RETURN_SOUR
# At least build the ROOT selection.xml by default for now
SET(ARG_IO_BACKEND_HANDLERS "ROOT")
ENDIF()

# Make sure that we re run the generation process everytime either the
# templates or the yaml file changes.
include(${podio_PYTHON_DIR}/templates/CMakeLists.txt)
set_property(
DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
${YAML_FILE}
${PODIO_TEMPLATES}
${podio_PYTHON_DIR}/podio_class_generator.py
${podio_PYTHON_DIR}/generator_utils.py
${podio_PYTHON_DIR}/podio_config_reader.py
)

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 ${CMAKE_COMMAND} -E echo "Creating \"${datamodel}\" data model"
COMMAND python ${podio_PYTHON_DIR}/podio_class_generator.py ${YAML_FILE} ${ARG_OUTPUT_FOLDER} ${datamodel} ${ARG_IO_BACKEND_HANDLERS}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE podio_generate_command_retval
)

file(GLOB headers ${ARG_OUTPUT_FOLDER}/${datamodel}/*.h)
file(GLOB sources ${ARG_OUTPUT_FOLDER}/src/*.cc)
IF(NOT ${podio_generate_command_retval} EQUAL 0)
message(FATAL_ERROR "Could not generate datamodel '${datamodel}'. Check your definition in '${YAML_FILE}'")
ENDIF()

# Get the generated headers and source files
include(${ARG_OUTPUT_FOLDER}/podio_generated_files.cmake)

set (${RETURN_HEADERS} ${headers} PARENT_SCOPE)
set (${RETURN_SOURCES} ${sources} PARENT_SCOPE)

add_custom_target(create_${datamodel}
COMMENT "Re-Creating \"${datamodel}\" data model"
DEPENDS ${YAML_FILE}
BYPRODUCTS ${sources} ${headers}
COMMAND python ${podio_PYTHON_DIR}/podio_class_generator.py --quiet ${YAML_FILE} ${ARG_OUTPUT_FOLDER} ${datamodel} ${ARG_IO_BACKEND_HANDLERS}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)

endfunction()


Expand Down
78 changes: 61 additions & 17 deletions python/podio_class_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from __future__ import unicode_literals, absolute_import, print_function

import os
import errno
import sys
import subprocess
from io import open
Expand Down Expand Up @@ -56,6 +55,24 @@ def get_clang_format():
return []


def write_file_if_changed(filename, content, force_write=False):
"""Write the file contents only if it has changed or if the file does not exist
yet. Return whether the file has been written or not"""
try:
with open(filename, 'r') as f:
existing_content = f.read()
changed = existing_content != content
except FileNotFoundError:
changed = True

if changed or force_write:
with open(filename, 'w') as f:
f.write(content)
return True

return False


class ClassGenerator(object):
def __init__(self, yamlfile, install_dir, package_name, io_handlers, verbose, dryrun):
self.install_dir = install_dir
Expand Down Expand Up @@ -84,6 +101,8 @@ def __init__(self, yamlfile, install_dir, package_name, io_handlers, verbose, dr
self.expose_pod_members = self.reader.options["exposePODMembers"]

self.clang_format = []
self.generated_files = []
self.any_changes = False

def process(self):
for name, component in self.reader.components.items():
Expand All @@ -96,6 +115,8 @@ def process(self):
self._create_selection_xml()
self.print_report()

self._write_cmake_lists_file()

def print_report(self):
if not self.verbose:
return
Expand Down Expand Up @@ -126,26 +147,13 @@ def _write_file(self, name, content):
else:
fullname = os.path.join(self.install_dir, "src", name)
if not self.dryrun:
self.generated_files.append(fullname)
if self.clang_format:
cfproc = subprocess.Popen(self.clang_format, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
content = cfproc.communicate(input=content.encode())[0].decode()

try:
with open(fullname, 'r') as f:
existing_content = f.read()
changed = existing_content != content

except EnvironmentError as e:
# If we deprecate python2 support, FileNotFoundError becomes available
# and this can be using it. For now we keep it compatible with both
# versions
if e.errno != errno.ENOENT:
raise
changed = True

if changed:
with open(fullname, 'w') as f:
f.write(content)
changed = write_file_if_changed(fullname, content)
self.any_changes = changed or self.any_changes

@staticmethod
def _get_filenames_templates(template_base, name):
Expand Down Expand Up @@ -377,6 +385,42 @@ def _get_member_includes(self, members):

return self._sort_includes(includes)

def _write_cmake_lists_file(self):
"""Write the names of all generated header and src files into cmake lists"""
header_files = (f for f in self.generated_files if f.endswith('.h'))
src_files = (f for f in self.generated_files if f.endswith('.cc'))
xml_files = (f for f in self.generated_files if f.endswith('.xml'))

def _write_list(name, target_folder, files, comment):
"""Write all files into a cmake variable using the target_folder as path to the
file"""
list_cont = []

list_cont.append(f'# {comment}')
list_cont.append(f'SET({name}')
for full_file in files:
fname = os.path.basename(full_file)
list_cont.append(f' {os.path.join(target_folder, fname)}')

list_cont.append(')')
list_cont.append(f'SET_PROPERTY(SOURCE ${{{name}}} PROPERTY GENERATED TRUE)\n')

return '\n'.join(list_cont)

full_contents = ['#-- AUTOMATICALLY GENERATED FILE - DO NOT EDIT -- \n']
full_contents.append(_write_list('headers', r'${ARG_OUTPUT_FOLDER}/${datamodel}',
header_files, 'Generated header files'))

full_contents.append(_write_list('sources', r'${ARG_OUTPUT_FOLDER}/src',
src_files, 'Generated source files'))

full_contents.append(_write_list('selection_xml', r'${ARG_OUTPUT_FOLDER}/src',
xml_files, 'Generated xml files'))

write_file_if_changed(f'{self.install_dir}/podio_generated_files.cmake',
'\n'.join(full_contents),
self.any_changes)

@staticmethod
def _is_pod_type(members):
"""Check if the members of the class define a POD type"""
Expand Down
23 changes: 23 additions & 0 deletions python/templates/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
set(PODIO_TEMPLATES
${CMAKE_CURRENT_LIST_DIR}/Collection.cc.jinja2
${CMAKE_CURRENT_LIST_DIR}/Collection.h.jinja2
${CMAKE_CURRENT_LIST_DIR}/CollectionData.cc.jinja2
${CMAKE_CURRENT_LIST_DIR}/CollectionData.h.jinja2
${CMAKE_CURRENT_LIST_DIR}/Component.h.jinja2
${CMAKE_CURRENT_LIST_DIR}/ConstObject.cc.jinja2
${CMAKE_CURRENT_LIST_DIR}/ConstObject.h.jinja2
${CMAKE_CURRENT_LIST_DIR}/Data.h.jinja2
${CMAKE_CURRENT_LIST_DIR}/Obj.cc.jinja2
${CMAKE_CURRENT_LIST_DIR}/Object.cc.jinja2
${CMAKE_CURRENT_LIST_DIR}/Object.h.jinja2
${CMAKE_CURRENT_LIST_DIR}/Obj.h.jinja2
${CMAKE_CURRENT_LIST_DIR}/selection.xml.jinja2
${CMAKE_CURRENT_LIST_DIR}/SIOBlock.cc.jinja2
${CMAKE_CURRENT_LIST_DIR}/SIOBlock.h.jinja2
${CMAKE_CURRENT_LIST_DIR}/macros/collections.jinja2
${CMAKE_CURRENT_LIST_DIR}/macros/declarations.jinja2
${CMAKE_CURRENT_LIST_DIR}/macros/implementations.jinja2
${CMAKE_CURRENT_LIST_DIR}/macros/iterator.jinja2
${CMAKE_CURRENT_LIST_DIR}/macros/sioblocks.jinja2
${CMAKE_CURRENT_LIST_DIR}/macros/utils.jinja2
)

0 comments on commit 760a9f5

Please sign in to comment.