diff --git a/cmake/ConfigureCrownlib.cmake b/cmake/ConfigureCrownlib.cmake index 134eb1ce..38353155 100644 --- a/cmake/ConfigureCrownlib.cmake +++ b/cmake/ConfigureCrownlib.cmake @@ -1,11 +1,18 @@ # build a shared lib from all CROWN functions include_directories(${CMAKE_SOURCE_DIR}/src) include_directories(${CMAKE_SOURCE_DIR}/include) + +include_directories(${CMAKE_SOURCE_DIR}/analysis_configurations/${ANALYSIS}/cpp_addons/src) +include_directories(${CMAKE_SOURCE_DIR}/analysis_configurations/${ANALYSIS}/cpp_addons/include) + file(GLOB SOURCES_1 ${CMAKE_SOURCE_DIR}/src/*.cxx) file(GLOB SOURCES_2 ${CMAKE_SOURCE_DIR}/src/utility/*.cxx ${CMAKE_SOURCE_DIR}/src/RecoilCorrections/*.cxx ${CMAKE_SOURCE_DIR}/src/SVFit/*.cxx) -set(SOURCES ${SOURCES_1} ${SOURCES_2}) + +file(GLOB SOURCES_3 ${CMAKE_SOURCE_DIR}/analysis_configurations/${ANALYSIS}/cpp_addons/src/*.cxx) + +set(SOURCES ${SOURCES_1} ${SOURCES_2} ${SOURCES_3}) if(BUILD_CROWNLIB_ONLY) message(STATUS "Building only the CROWNLIB library") diff --git a/code_generation/analysis_template.cxx b/code_generation/analysis_template.cxx index 5bdf5e7d..8e787a04 100644 --- a/code_generation/analysis_template.cxx +++ b/code_generation/analysis_template.cxx @@ -32,6 +32,8 @@ #include "include/triggers.hxx" #include "include/fakefactors.hxx" +// {INCLUDE_ANALYSISADDONS} + // {INCLUDES} int main(int argc, char *argv[]) { diff --git a/code_generation/analysis_template_friends.cxx b/code_generation/analysis_template_friends.cxx index f4d864ae..69810249 100644 --- a/code_generation/analysis_template_friends.cxx +++ b/code_generation/analysis_template_friends.cxx @@ -32,6 +32,9 @@ #include "include/topreco.hxx" #include "include/triggers.hxx" #include "include/tripleselection.hxx" + +// {INCLUDE_ANALYSISADDONS} + // {INCLUDES} int validate_rootfile(std::string file, std::string &basetree) { diff --git a/code_generation/code_generation.py b/code_generation/code_generation.py index b50d0f5f..6593831e 100644 --- a/code_generation/code_generation.py +++ b/code_generation/code_generation.py @@ -12,6 +12,32 @@ log = logging.getLogger(__name__) +def addon_includes(analysis_name: str, file_name: str) -> str: + """ + Add the includes all .hxx files from analysis configuration folder: + analysis_configurations/{analysis_name}/cpp_addons/include + Args: + analysis_name: the name of the analysis + file_name: Name of file that is templated + Returns: + str - the include statements for the cpp addons + """ + path = f"analysis_configurations/{analysis_name}/cpp_addons/include" + if os.path.exists(path) and os.path.isdir(path) and os.listdir(path): + log.debug( + f"Adding addons from {path} to {file_name}: {' '.join(os.listdir(path))}" + ) + paths = "\n".join( + f'#include "{os.path.abspath(os.path.join(path, item))}"' + for item in os.listdir(path) + if item.endswith(".hxx") + ) + return paths + else: + log.debug(f"No addons found in {path}") + return "" + + class CodeSubset(object): """ Class used to generate code for a smaller subset. For each subset, a new object must be created. @@ -24,6 +50,7 @@ class CodeSubset(object): folder: The folder in which the code will be generated. parameters: The parameters to be used for the generation. name: The name of the code subset. + analysis_name: Name of the analysis configuration. Returns: None @@ -38,6 +65,7 @@ def __init__( folder: str, configuration_parameters: Dict[str, Any], name: str, + analysis_name: str, ): self.file_name = file_name self.template = template @@ -48,6 +76,7 @@ def __init__( self.count = 0 self.folder = folder self.commands: List[str] = [] + self.analysis_name = analysis_name self.headerfile = os.path.join( self.folder, "include", self.scope, "{}.hxx".format(self.file_name) ) @@ -120,8 +149,11 @@ def write(self): with open(self.sourcefile + ".new", "w") as f: commandstring = "".join(self.commands) f.write( - self.template.replace("// { commands }", commandstring).replace( - "{subsetname}", self.name + self.template.replace("// { commands }", commandstring) + .replace("{subsetname}", self.name) + .replace( + "// {INCLUDE_ANALYSISADDONS}", + addon_includes(self.analysis_name, self.file_name), ) ) if os.path.isfile(self.sourcefile): @@ -350,6 +382,10 @@ def write_code(self, calls: str, includes: str, run_commands: str) -> None: " // {ZERO_EVENTS_FALLBACK}", self.zero_events_fallback() ) .replace(" // {CODE_GENERATION}", calls) + .replace( + "// {INCLUDE_ANALYSISADDONS}", + addon_includes(self.analysis_name, self.executable_name + ".cxx"), + ) .replace("// {INCLUDES}", includes) .replace(" // {RUN_COMMANDS}", run_commands) .replace("// {MULTITHREADING}", threadcall) @@ -458,6 +494,7 @@ def generate_subsets(self, scope: str) -> None: ), configuration_parameters=self.configuration.config_parameters[scope], name=producer_name + "_" + scope, + analysis_name=self.analysis_name, ) subset.create() subset.write() diff --git a/code_generation/subset_template.cxx b/code_generation/subset_template.cxx index 15778dbc..2eb6ee50 100644 --- a/code_generation/subset_template.cxx +++ b/code_generation/subset_template.cxx @@ -31,6 +31,9 @@ #include "include/topreco.hxx" #include "include/triggers.hxx" #include "include/fakefactors.hxx" + +// {INCLUDE_ANALYSISADDONS} + ROOT::RDF::RNode {subsetname} (ROOT::RDF::RNode df0, OnnxSessionManager &onnxSessionManager, correctionManager::CorrectionManager &correctionManager) { // { commands } diff --git a/docs/sphinx_source/cpp_addons.rst b/docs/sphinx_source/cpp_addons.rst new file mode 100644 index 00000000..311ca5c6 --- /dev/null +++ b/docs/sphinx_source/cpp_addons.rst @@ -0,0 +1,61 @@ +C++ Add-ons +========== + +In some cases, the core codebase of CROWN (CROWNLIB) may not include all the features required for an analysis. To address this, users can add custom C++ code within their analysis configurations. These add-ons are automatically integrated to the C++ code during the code generation process. + +Location and directory structure +-------------------------------- + +The expected structure within the analysis configuration is as follows: + +.. code-block:: console + + analysis_configurations + └── + └── cpp_addons + ├── include + │ ├── .hxx + │ ├── .hxx + │ └── ... + └── src + ├── .cxx + ├── .cxx + └── ... + + +If an analysis does not require any additional C++ code and can rely solely on CROWNLIB, the ``cpp_addons`` folder can be omitted entirely from the analysis configuration. + +``.cxx`` and ``.hxx`` File structure +------------------------------------ + +This functionality considers files in ``analysis_configuration//cpp_addons/src`` and ``analysis_configuration//cpp_addons/include`` during compilation. The following points should be followed when adding and using custom C++ code: + +* Use unique guards for each ``.cxx`` file you introduce, especially concerning CROWNLIB. For the corresponding ``.hxx`` file(s), the same unique guard(s) should be applied. +* Use a unique function name or function signature if the custom function needs to reside in a namespace that already exists in CROWNLIB +* Use ``../../../../include/.hxx`` if you explicitly want to import functionality from CROWNLIB. Importing CROWNLIB files using different relative paths can lead to unexpected behavior. + +A example ``.cxx`` file could have the following structure: + + +.. code-block:: cpp + + #ifndef UNIQUE_GUARD_NAME_H // unique w.r.t. CROWNLIB and other files in cpp_addons + #define UNIQUE_GUARD_NAME_H + + // Include CROWNLIB functionalities + #include "../../../../include/utility/CorrectionManager.hxx" + #include "../../../../include/utility/Logger.hxx" + + // Feature.hxx file defined in cpp_addons + #include "../Feature.hxx" + + // Globally present, i.e. from the ROOT framework + #include "ROOT/RDataFrame.hxx" + #include "correction.h" + + /* Your code here */ + + // End of the file + #endif // UNIQUE_GUARD_NAME_H + + diff --git a/docs/sphinx_source/index.rst b/docs/sphinx_source/index.rst index 0db17070..561edbd9 100644 --- a/docs/sphinx_source/index.rst +++ b/docs/sphinx_source/index.rst @@ -50,6 +50,7 @@ Documentation contrib.rst py_configuration.rst correction_manager.rst + cpp_addons.rst .. toctree:: :maxdepth: 2