Source code for ElementsKernel.AddCppClass

#
# Copyright (C) 2012-2020 Euclid Science Ground Segment
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3.0 of the License, or (at your option)
# any later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#

""" This script creates a new Elements C++ Class

:file: ElementsKernel/AddCppClass.py
:author: Nicolas Morisset

:date: 01/07/15



"""

import argparse
import os
import time

from ElementsKernel import Auxiliary
from ElementsKernel import ProjectCommonRoutines
from ElementsKernel import ParseCmakeListsMacros
from ElementsKernel import Logging

from ElementsKernel import Exit

LOGGER = Logging.getLogger(__name__)

# Define constants
CMAKE_LISTS_FILE = 'CMakeLists.txt'
H_TEMPLATE_FILE = 'ClassName_template.h'
CPP_TEMPLATE_FILE = 'ClassName_template.cpp'
TPP_TEMPLATE_FILE = 'ClassTpp_template.tpp'
UNITTEST_TEMPLATE_FILE = 'UnitTestFile_template.cpp'

H_TEMPLATE_FILE_IN = 'ClassName_template.h.in'
TPP_TEMPLATE_FILE_IN = 'ClassTpp_template.tpp.in'
CPP_TEMPLATE_FILE_IN = 'ClassName_template.cpp.in'
UNITTEST_TEMPLATE_FILE_IN = 'UnitTestFile_template.cpp.in'

################################################################################


[docs]def getClassName(subdir_class): """ Get the class name and sub directory if any """ (subdir, class_name) = os.path.split(subdir_class) LOGGER.info('Class name: %s', class_name) if subdir: LOGGER.info('Sub directory: %s', subdir) return subdir, class_name
################################################################################
[docs]def createDirectories(module_dir, module_name, subdir, opt_template): """ Create directories needed for a module and a class """ standalone_directories = [os.path.join(module_name, subdir), os.path.join('src', 'lib', subdir), os.path.join('tests', 'src', subdir)] if opt_template: standalone_directories.append(os.path.join(module_name, '_impl')) for d in standalone_directories: target_dir = os.path.join(module_dir, d) if not os.path.exists(target_dir): os.makedirs(target_dir)
################################################################################
[docs]def substituteAuxFiles(module_dir, class_name, module_name, subdir, opt_visibility, opt_template): """ Copy AUX file(s) and substitutes keyworks """ module_name_subdir = module_name if subdir: module_name_subdir = os.path.join(module_name, subdir) target_locations = { H_TEMPLATE_FILE_IN: os.path.join(module_name_subdir, class_name + ".h"), CPP_TEMPLATE_FILE_IN: os.path.join('src', 'lib', subdir, class_name + ".cpp"), UNITTEST_TEMPLATE_FILE_IN: os.path.join('tests', 'src', subdir, class_name + "_test.cpp") } if opt_template: target_locations[ TPP_TEMPLATE_FILE_IN] = os.path.join(module_name_subdir, '_impl', class_name + ".tpp") # Visibility option visibility_include = "" visibility_macro = "" if opt_visibility == "simple": visibility_macro = "ELEMENTS_API" visibility_include = '#include "ElementsKernel/Export.h"' elif opt_visibility == "native": visibility_macro = module_name.upper() + "_EXPORT" visibility_include = '#include "' + module_name + '_export.h"' # Set keywords empty if Template option not set template_declaration = "" template_comment = "" template_func = "" template_extension = "" template_include = "" template_defwords = "" template_undefwords = "" template_comment_cpp = "" template_class_cpp = "" defwords = ("_" + module_name + "_" + class_name).upper() tdefwords = "_" + (module_name).upper() + defwords + "_IMPL" # Template option set if opt_template: template_declaration = "template<typename T>" template_comment = "//// instantiation of the most expected types (declaration)" template_extension = "// extern template " + visibility_macro + " " + class_name + "<double>;" template_func = "// void fakeFunc(const T& t);" template_defwords = "#define " + tdefwords template_include = "#include \"" + os.path.join(module_name_subdir, "_impl", class_name + ".tpp\"") template_undefwords = "#undef " + tdefwords template_comment_cpp = "//// instantiation of the most expected types" template_class_cpp = "// template " + class_name + "<double>;" configuration = {"FILE_H": os.path.join(module_name, subdir, class_name + '.h'), "FILE_CPP": os.path.join('src', 'lib', subdir, class_name + '.cpp'), "FILE_TEST": os.path.join('tests', 'src', subdir, class_name + '_test.cpp'), "FILE_TPP": os.path.join(module_name, subdir, "_impl", class_name + '.tpp'), "DATE": time.strftime("%x"), "AUTHOR": ProjectCommonRoutines.getAuthor(), "DEFINE_WORDS": defwords + "_H", "CLASSNAME": class_name, "OSSEP": os.sep, "MODULENAME": module_name, "MODULENAME_SUBDIR": module_name_subdir, "VISIBILITY_INCLUDE": visibility_include, "VISIBILITY_MACRO": visibility_macro, "TEMPLATE_DECLARATION": template_declaration, "TEMPLATE_FUNC": template_func, "TEMPLATE_COMMENT": template_comment, "TEMPLATE_EXTENSION": template_extension, "TEMPLATE_DEFWORDS": template_defwords, "TEMPLATE_TPP_DEFWORDS": tdefwords, "TEMPLATE_INCLUDE": template_include, "TEMPLATE_UNDEFWORDS": template_undefwords, "TEMPLATE_COM_CPP": template_comment_cpp, "TEMPLATE_CLASS_CPP": template_class_cpp } # Put AUX files to their target for src in target_locations: file_name = os.path.join("ElementsKernel", "templates", src) tgt = target_locations[src] Auxiliary.configure(file_name, module_dir, tgt, configuration=configuration, create_missing_dir=True) ProjectCommonRoutines.addItemToCreationList(os.path.join(module_dir, tgt))
# # ###############################################################################
[docs]def updateCmakeListsFile(module_dir, subdir, class_name, elements_dep_list, library_dep_list): """ Update the <CMakeLists.txt> file for a class """ LOGGER.info('Updating the <%s> file', CMAKE_LISTS_FILE) cmake_filename = os.path.join(module_dir, CMAKE_LISTS_FILE) ProjectCommonRoutines.addItemToCreationList(cmake_filename) # Cmake file already exist if os.path.isfile(cmake_filename): cmake_object, module_name = ProjectCommonRoutines.updateCmakeCommonPart(cmake_filename, library_dep_list) # Put ElementsKernel as a default default_dependency = 'ElementsKernel' if elements_dep_list: if not default_dependency in elements_dep_list: elements_dep_list.insert(0, default_dependency) else: elements_dep_list = [default_dependency] # Update ElementsDependsOnSubdirs macro if elements_dep_list: for mod_dep in elements_dep_list: dep_object = ParseCmakeListsMacros.ElementsDependsOnSubdirs([mod_dep]) cmake_object.elements_depends_on_subdirs_list.append(dep_object) # Update elements_add_library macro if module_name: source = os.path.join('src', 'lib', subdir, '*.cpp') existing = [x for x in cmake_object.elements_add_library_list if x.name == module_name] link_libs = [] include_dirs = [] if elements_dep_list: link_libs = link_libs + elements_dep_list include_dirs = include_dirs + elements_dep_list if library_dep_list: link_libs = link_libs + library_dep_list include_dirs = include_dirs + library_dep_list if existing: if not source in existing[0].source_list: existing[0].source_list.append(source) for lib in link_libs: if not lib in existing[0].link_libraries_list: existing[0].link_libraries_list.append(lib) for incd in include_dirs: if not incd in existing[0].include_dirs_list: existing[0].include_dirs_list.append(incd) else: source_list = [source] public_headers_list = [module_name] lib_object = ParseCmakeListsMacros.ElementsAddLibrary(module_name, source_list, link_libs, include_dirs, public_headers_list) cmake_object.elements_add_library_list.append(lib_object) # Add unit test source_name = os.path.join('tests', 'src', subdir, class_name + '_test.cpp') if subdir: exec_test_name = module_name + "_" + subdir + "_" + class_name + '_test' test_name = subdir + "_" + class_name else: exec_test_name = module_name + "_" + class_name + '_test' test_name = class_name unittest_object = ParseCmakeListsMacros.ElementsAddUnitTest(test_name, [source_name], [module_name], [], 'Boost', exec_test_name) cmake_object.elements_add_unit_test_list.append(unittest_object) # Write new data f = open(cmake_filename, 'w') f.write(str(cmake_object)) f.close()
################################################################################
[docs]def checkClassFileNotExist(class_name, module_dir, module_name, subdir): """ Check if the class file does not already exist """ module_path = os.path.join(module_dir, module_name, subdir) file_name = class_name + '.h' file_name_path = os.path.join(module_path, file_name) if os.path.exists(file_name_path): full_name = os.path.join(subdir, class_name) raise Exception("The <%s> class already exists! " "Header file found here : < %s >" % (full_name, file_name_path))
################################################################################
[docs]def createCppClass(module_dir, module_name, subdir, class_name, elements_dep_list, library_dep_list, opt_visibility, opt_template): """ Create all necessary files for a cpp class """ # Check the class does not exist already checkClassFileNotExist(class_name, module_dir, module_name, subdir) createDirectories(module_dir, module_name, subdir, opt_template) # Update cmake file updateCmakeListsFile(module_dir, subdir, class_name, elements_dep_list, library_dep_list) # Substitue strings in files substituteAuxFiles(module_dir, class_name, module_name, subdir, opt_visibility, opt_template)
################################################################################
[docs]def makeChecks(): """ Make some checks """ # Check aux files exist ProjectCommonRoutines.checkAuxFileExist(H_TEMPLATE_FILE_IN) ProjectCommonRoutines.checkAuxFileExist(CPP_TEMPLATE_FILE_IN)
################################################################################
[docs]def defineSpecificProgramOptions(): """ Define program option(s) """ description = """ This script creates an <Elements> class at your current directory (default). All necessary structure (directory structure, makefiles etc...) will be automatically created for you if any but you have to be inside an <Elements> module. You can specify a sub-directory where you want your class files (.h, .cpp). e.g AddCppClass class_name or AddCppClass subdir/class_name """ from argparse import RawTextHelpFormatter parser = argparse.ArgumentParser(description=description, formatter_class=RawTextHelpFormatter) parser.add_argument('class_name', metavar='class-name', type=str, help='Class name without extention. e.g my_class_name or subdir/my_class_name') parser.add_argument('-ed', '--elements-dependency', metavar='module_name', action='append', type=str, help='Dependency module name e.g. "-ed ElementsKernel"') parser.add_argument('-extd', '--external-dependency', metavar='library_name', action='append', type=str, help='External dependency library name e.g. "-extd ElementsKernel"') parser.add_argument('-V', '--visibility', metavar='visibility', type=str, choices=['simple', 'native'], help="Class Visibility, possible values: simple or native \n" "simple : ELEMENTS_API is added to the <.h>. This is the mode supported by Elements\n" "native : <library>_EXPORTS is added (like ElementsKernel_EXPORTS)\n" " This is the native mode supported by CMake" ) parser.add_argument('-t', '--template', default=False, action='store_true', help="Add the support for the templated class creation" ) return parser
################################################################################
[docs]def mainMethod(args): """ Main method of the AddCppClass Script""" LOGGER.info('#') LOGGER.info('# Logging from the mainMethod() of the AddCppClass script ') LOGGER.info('#') exit_code = Exit.Code["OK"] elements_dep_list = args.elements_dependency library_dep_list = args.external_dependency (subdir, class_name) = getClassName(args.class_name) opt_visibility = args.visibility opt_template = args.template try: # Default is the current directory module_dir = os.getcwd() # Make checks makeChecks() # We absolutely need a Elements cmake file module_name = ProjectCommonRoutines.getElementsModuleName(module_dir) LOGGER.info('Current directory : %s', module_dir) LOGGER.info('') # Create CPP class createCppClass(module_dir, module_name, subdir, class_name, elements_dep_list, library_dep_list, opt_visibility, opt_template) LOGGER.info('<%s> class successfully created in <%s>.', class_name, os.path.join(module_dir, subdir)) # Remove backup file ProjectCommonRoutines.deleteFile(os.path.join(module_dir, CMAKE_LISTS_FILE) + '~') # Print all files created ProjectCommonRoutines.printCreationList() except Exception as msg: if str(msg): LOGGER.error(msg) LOGGER.error('# Script aborted.') exit_code = Exit.Code["NOT_OK"] else: LOGGER.info('# Script over.') return exit_code