diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000000000000000000000000000000000000..7a01a2d6c0caf880738df8393567fb169a07be7e --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +include MANIFEST.in +include LICENSE +include README.md +recursive-include aidge_core * +include setup.py +include version.txt diff --git a/aidge_core/__init__.py b/aidge_core/__init__.py index 4b5c448355a17fd4274ba45f5cd98afa70b1ae53..1d32222e1c148f2eba3f351fc43619c56a7d65a3 100644 --- a/aidge_core/__init__.py +++ b/aidge_core/__init__.py @@ -10,3 +10,4 @@ SPDX-License-Identifier: EPL-2.0 from aidge_core.aidge_core import * # import so generated by PyBind from aidge_core.export import ExportNode, generate_file, generate_str import aidge_core.utils +from aidge_core.aidge_export_aidge import * diff --git a/aidge_core/aidge_export_aidge/__init__.py b/aidge_core/aidge_export_aidge/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6dc611fc4e55450390cf2683dee0d8d4b1aa7169 --- /dev/null +++ b/aidge_core/aidge_export_aidge/__init__.py @@ -0,0 +1,9 @@ + +from pathlib import Path + +# Constants +FILE = Path(__file__).resolve() +ROOT_EXPORT = FILE.parents[0] + +from .operator_export import * +from .export import export diff --git a/aidge_core/aidge_export_aidge/export.py b/aidge_core/aidge_export_aidge/export.py new file mode 100644 index 0000000000000000000000000000000000000000..d2320aa0c383c7e26c7d7eaca7dcc12c0b63de9d --- /dev/null +++ b/aidge_core/aidge_export_aidge/export.py @@ -0,0 +1,47 @@ +import aidge_core +import shutil +import os +from .utils import supported_operators, OPERATORS_REGISTRY +from . import ROOT_EXPORT + + + +def export(export_folder: str, + graphview: aidge_core.GraphView, + scheduler: aidge_core.Scheduler): + + # Create export directory + os.makedirs(export_folder, exist_ok=True) + + # Cpy static files + shutil.copytree(ROOT_EXPORT / "static/", export_folder, dirs_exist_ok=True) + + ordered_nodes = scheduler.get_static_scheduling() + + list_configs = [] # List of headers to include in dnn.cpp to access attribute and parameters + list_actions = [] # List of string to construct graph + set_operator = set() + for node in ordered_nodes: + if node.type() in supported_operators(): + set_operator.add(node.type()) + op = OPERATORS_REGISTRY[node.type()](node) + + # Export the configuration + list_configs = op.export(export_folder, list_configs) + + # Add forward kernel + list_actions = op.forward(list_actions) + else: + # TODO: change to raise once dev ended + print(f"Operator: {node.type()} is not supported") + # raise RuntimeError(f"Operator: {node.type()} is not supported") + + # Generate full dnn.cpp + aidge_core.generate_file( + f"{export_folder}/src/dnn.cpp", + str(ROOT_EXPORT) + "/templates/dnn.jinja", + headers=list_configs, + operators=set_operator, + actions=list_actions, + ) + diff --git a/aidge_core/aidge_export_aidge/operator_export/__init__.py b/aidge_core/aidge_export_aidge/operator_export/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..37d674ac84f72d643ba1a628a86fbcde9780f4a4 --- /dev/null +++ b/aidge_core/aidge_export_aidge/operator_export/__init__.py @@ -0,0 +1,14 @@ +""" +Copyright (c) 2023 CEA-List + +This program and the accompanying materials are made available under the +terms of the Eclipse Public License 2.0 which is available at +http://www.eclipse.org/legal/epl-2.0. + +SPDX-License-Identifier: EPL-2.0 +""" +from pathlib import Path + +DIR_PATH = Path(__file__).parent +modules = [Path(module).stem for module in DIR_PATH.glob("*.py")] +__all__ = [ f for f in modules if f != "__init__"] diff --git a/aidge_core/aidge_export_aidge/operator_export/conv.py b/aidge_core/aidge_export_aidge/operator_export/conv.py new file mode 100644 index 0000000000000000000000000000000000000000..e3ce0ff98cb49b4c6a0743188d2e0405b90ca284 --- /dev/null +++ b/aidge_core/aidge_export_aidge/operator_export/conv.py @@ -0,0 +1,39 @@ +from aidge_core.aidge_export_aidge.utils import operator_register, parse_node_input +from aidge_core.aidge_export_aidge import ROOT_EXPORT +from aidge_core import ExportNode, generate_file, generate_str +import os + + +@operator_register("Conv") +class Conv(ExportNode): + def __init__(self, node): + super().__init__(node) + + + def export(self, export_folder:str, list_configs:list): + + name = f"{self.name}" + include_path = f"attributes/{name}.hpp" + filepath = f"{export_folder}/include/{include_path}" + dirname = os.path.dirname(filepath) + + # If directory doesn't exist, create it + if not os.path.exists(dirname): os.makedirs(dirname) + + generate_file( + filepath, + str(ROOT_EXPORT) + "/templates/attributes/conv.jinja", + name=self.name, + **self.attributes + ) + list_configs.append(include_path) + return list_configs + + def forward(self, list_actions:list): + list_actions.append(generate_str( + str(ROOT_EXPORT) + "/templates/graph_ctor/conv.jinja", + name=self.name, + inputs=parse_node_input(self.node.inputs()), + **self.attributes + )) + return list_actions diff --git a/aidge_core/aidge_export_aidge/operator_export/fc.py b/aidge_core/aidge_export_aidge/operator_export/fc.py new file mode 100644 index 0000000000000000000000000000000000000000..0d0d60ea10bcc1b74dc3f827dbe2d192486df182 --- /dev/null +++ b/aidge_core/aidge_export_aidge/operator_export/fc.py @@ -0,0 +1,40 @@ +from aidge_core.aidge_export_aidge.utils import operator_register,parse_node_input +from aidge_core.aidge_export_aidge import ROOT_EXPORT +from aidge_core import ExportNode, generate_file, generate_str +import os + + +@operator_register("FC") +class FC(ExportNode): + def __init__(self, node): + super().__init__(node) + + + def export(self, export_folder:str, list_configs:list): + + name = f"{self.name}" + include_path = f"attributes/{name}.hpp" + filepath = f"{export_folder}/include/{include_path}" + dirname = os.path.dirname(filepath) + + # If directory doesn't exist, create it + if not os.path.exists(dirname): os.makedirs(dirname) + + generate_file( + filepath, + str(ROOT_EXPORT) + "/templates/attributes/fc.jinja", + name=self.name, + InChannels=self.inputs_dims[1][1], + **self.attributes + ) + list_configs.append(include_path) + return list_configs + + def forward(self, list_actions:list): + list_actions.append(generate_str( + str(ROOT_EXPORT) + "/templates/graph_ctor/fc.jinja", + name=self.name, + inputs=parse_node_input(self.node.inputs()), + **self.attributes + )) + return list_actions diff --git a/aidge_core/aidge_export_aidge/operator_export/maxpooling.py b/aidge_core/aidge_export_aidge/operator_export/maxpooling.py new file mode 100644 index 0000000000000000000000000000000000000000..ad26fa3c68c193213f56ff7d15939a6368808938 --- /dev/null +++ b/aidge_core/aidge_export_aidge/operator_export/maxpooling.py @@ -0,0 +1,39 @@ +from aidge_core.aidge_export_aidge.utils import operator_register, parse_node_input +from aidge_core.aidge_export_aidge import ROOT_EXPORT +from aidge_core import ExportNode, generate_file, generate_str +import os + + +@operator_register("MaxPooling") +class MaxPooling(ExportNode): + def __init__(self, node): + super().__init__(node) + + + def export(self, export_folder:str, list_configs:list): + + name = f"{self.name}" + include_path = f"attributes/{name}.hpp" + filepath = f"{export_folder}/include/{include_path}" + dirname = os.path.dirname(filepath) + + # If directory doesn't exist, create it + if not os.path.exists(dirname): os.makedirs(dirname) + + generate_file( + filepath, + str(ROOT_EXPORT) + "/templates/attributes/maxpooling.jinja", + name=self.name, + **self.attributes + ) + list_configs.append(include_path) + return list_configs + + def forward(self, list_actions:list): + list_actions.append(generate_str( + str(ROOT_EXPORT) + "/templates/graph_ctor/maxpooling.jinja", + name=self.name, + inputs=parse_node_input(self.node.inputs()), + **self.attributes + )) + return list_actions diff --git a/aidge_core/aidge_export_aidge/operator_export/producer.py b/aidge_core/aidge_export_aidge/operator_export/producer.py new file mode 100644 index 0000000000000000000000000000000000000000..c8435db173f6f87d7bfbc68276e5908f5ee47d52 --- /dev/null +++ b/aidge_core/aidge_export_aidge/operator_export/producer.py @@ -0,0 +1,49 @@ +from aidge_core.aidge_export_aidge.utils import operator_register +from aidge_core.aidge_export_aidge import ROOT_EXPORT +from aidge_core import ExportNode, generate_file, generate_str +import numpy as np +import os + + +@operator_register("Producer") +class Producer(ExportNode): + """ + If there is a standardization of the export operators + then this class should be just a inheritance of ProducerCPP + """ + def __init__(self, node): + super().__init__(node) + child, in_idx = self.node.output(0)[0] + self.tensor_name = f"{child.name()}_{in_idx}" + self.values = np.array(self.operator.get_output(0)) + + def export(self, export_folder:str, list_configs:list): + assert(len(self.node.output(0)) == 1) + + include_path = f"parameters/{self.tensor_name}.hpp" + filepath = f"{export_folder}/include/{include_path}" + + dirname = os.path.dirname(filepath) + + # If directory doesn't exist, create it + if not os.path.exists(dirname): os.makedirs(dirname) + aidge_tensor = self.operator.get_output(0) + generate_file( + filepath, + str(ROOT_EXPORT) + "/templates/parameter.jinja", + dims = aidge_tensor.dims(), + data_t = "float", # TODO : get data from producer + name = self.tensor_name, + values = str(aidge_tensor) + ) + list_configs.append(include_path) + return list_configs + + def forward(self, list_actions:list): + list_actions.append(generate_str( + str(ROOT_EXPORT) + "/templates/graph_ctor/producer.jinja", + name=self.name, + tensor_name=self.tensor_name, + **self.attributes + )) + return list_actions diff --git a/aidge_core/aidge_export_aidge/operator_export/relu.py b/aidge_core/aidge_export_aidge/operator_export/relu.py new file mode 100644 index 0000000000000000000000000000000000000000..0d18e6a7409f406aec5bfd19c2f559d674aea5ad --- /dev/null +++ b/aidge_core/aidge_export_aidge/operator_export/relu.py @@ -0,0 +1,21 @@ +from aidge_core.aidge_export_aidge.utils import operator_register, parse_node_input +from aidge_core import ExportNode, generate_str +from aidge_core.aidge_export_aidge import ROOT_EXPORT + +@operator_register("ReLU") +class ReLU(ExportNode): + def __init__(self, node): + super().__init__(node) + + + def export(self, export_folder:str, list_configs:list): + return list_configs + + def forward(self, list_actions:list): + list_actions.append(generate_str( + str(ROOT_EXPORT) + "/templates/graph_ctor/relu.jinja", + name=self.name, + inputs=parse_node_input(self.node.inputs()), + **self.attributes + )) + return list_actions diff --git a/aidge_core/aidge_export_aidge/static/CMakeLists.txt b/aidge_core/aidge_export_aidge/static/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..272d367d8de21a08765d6fe1a10355a68c0d308b --- /dev/null +++ b/aidge_core/aidge_export_aidge/static/CMakeLists.txt @@ -0,0 +1,149 @@ +cmake_minimum_required(VERSION 3.15) + + +file(STRINGS "${CMAKE_SOURCE_DIR}/version.txt" version) +file(STRINGS "${CMAKE_SOURCE_DIR}/project_name.txt" project) + +message(STATUS "Project name: ${project}") +message(STATUS "Project version: ${version}") + +# Note : project name is {project} and python module name is also {project} +set(module_name _${project}) # target name + +project(${project}) +set(CXX_STANDARD 14) + +############################################## +# Define options +option(PYBIND "python binding" ON) +option(WERROR "Warning as error" OFF) +option(TEST "Enable tests" ON) +option(COVERAGE "Enable coverage" OFF) +option(ENABLE_ASAN "Enable ASan (AddressSanitizer) for runtime analysis of memory use (over/underflow, memory leak, ...)" OFF) + +############################################## +# Import utils CMakeLists +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") +include(PybindModuleCreation) + +if(CMAKE_COMPILER_IS_GNUCXX AND COVERAGE) + Include(CodeCoverage) +endif() + +############################################## +# Find system dependencies +find_package(aidge_core REQUIRED) +find_package(aidge_backend_cpu REQUIRED) # TODO remove backend from here ? + +############################################## +# Create target and set properties +file(GLOB_RECURSE src_files "src/*.cpp") +file(GLOB_RECURSE inc_files "include/*.hpp") + +add_library(${module_name} ${src_files} ${inc_files}) +target_link_libraries(${module_name} + PUBLIC + _aidge_core # _ is added because we link the target not the project + _aidge_backend_cpu +) + +#Set target properties +set_property(TARGET ${module_name} PROPERTY POSITION_INDEPENDENT_CODE ON) + +if( ${ENABLE_ASAN} ) + message("Building ${module_name} with ASAN.") + set(SANITIZE_FLAGS -fsanitize=address -fno-omit-frame-pointer) + target_link_libraries(${module_name} + PUBLIC + -fsanitize=address + ) + target_compile_options(${module_name} + PRIVATE + ${SANITIZE_FLAGS} + ) +endif() + +target_include_directories(${module_name} + PUBLIC + $<INSTALL_INTERFACE:include> + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +# PYTHON BINDING +if (PYBIND) + generate_python_binding(${project} ${module_name}) + + # Handles Python + pybind11 headers dependencies + target_link_libraries(${module_name} + PUBLIC + pybind11::pybind11 + PRIVATE + Python::Python + ) +endif() + +target_compile_features(${module_name} PRIVATE cxx_std_14) + +target_compile_options(${module_name} PRIVATE + $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>: + -Wall -Wextra -Wold-style-cast -Winline -pedantic -Werror=narrowing -Wshadow $<$<BOOL:${WERROR}>:-Werror>>) +target_compile_options(${module_name} PRIVATE + $<$<CXX_COMPILER_ID:MSVC>: + /W4>) + +if(CMAKE_COMPILER_IS_GNUCXX AND COVERAGE) + append_coverage_compiler_flags() +endif() + +############################################## +# Installation instructions + +include(GNUInstallDirs) +set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${project}) + +install(TARGETS ${module_name} EXPORT ${project}-targets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +#Export the targets to a script + +install(EXPORT ${project}-targets + FILE "${project}-targets.cmake" + DESTINATION ${INSTALL_CONFIGDIR} + COMPONENT ${module_name} +) + +#Create a ConfigVersion.cmake file +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/${project}-config-version.cmake" + VERSION ${version} + COMPATIBILITY AnyNewerVersion +) + +configure_package_config_file("${project}-config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/${project}-config.cmake" + INSTALL_DESTINATION ${INSTALL_CONFIGDIR} +) + +#Install the config, configversion and custom find modules +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/${project}-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${project}-config-version.cmake" + DESTINATION ${INSTALL_CONFIGDIR} +) + +############################################## +## Exporting from the build tree +export(EXPORT ${project}-targets + FILE "${CMAKE_CURRENT_BINARY_DIR}/${project}-targets.cmake") + +# Compile executable +add_executable(main main.cpp) +target_link_libraries(main PUBLIC _aidge_core ${module_name}) diff --git a/aidge_core/aidge_export_aidge/static/README.md b/aidge_core/aidge_export_aidge/static/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1ce48d5274cbc2f007bde7c7be01e41e617cb19a --- /dev/null +++ b/aidge_core/aidge_export_aidge/static/README.md @@ -0,0 +1,5 @@ +To compile: + +> mkdir build && cd build +> cmake -DCMAKE_INSTALL_PREFIX:PATH=/data1/is156025/cm264821/anaconda3/envs/aidge_demo/lib/libAidge .. +> make all install diff --git a/aidge_core/aidge_export_aidge/static/cmake/CodeCoverage.cmake b/aidge_core/aidge_export_aidge/static/cmake/CodeCoverage.cmake new file mode 100644 index 0000000000000000000000000000000000000000..d4a039fd0e511238df1c0e0502c7588409099289 --- /dev/null +++ b/aidge_core/aidge_export_aidge/static/cmake/CodeCoverage.cmake @@ -0,0 +1,742 @@ +# Copyright (c) 2012 - 2017, Lars Bilke +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# CHANGES: +# +# 2012-01-31, Lars Bilke +# - Enable Code Coverage +# +# 2013-09-17, Joakim Söderberg +# - Added support for Clang. +# - Some additional usage instructions. +# +# 2016-02-03, Lars Bilke +# - Refactored functions to use named parameters +# +# 2017-06-02, Lars Bilke +# - Merged with modified version from github.com/ufz/ogs +# +# 2019-05-06, Anatolii Kurotych +# - Remove unnecessary --coverage flag +# +# 2019-12-13, FeRD (Frank Dana) +# - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor +# of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments. +# - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY +# - All setup functions: accept BASE_DIRECTORY, EXCLUDE list +# - Set lcov basedir with -b argument +# - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be +# overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().) +# - Delete output dir, .info file on 'make clean' +# - Remove Python detection, since version mismatches will break gcovr +# - Minor cleanup (lowercase function names, update examples...) +# +# 2019-12-19, FeRD (Frank Dana) +# - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets +# +# 2020-01-19, Bob Apthorpe +# - Added gfortran support +# +# 2020-02-17, FeRD (Frank Dana) +# - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters +# in EXCLUDEs, and remove manual escaping from gcovr targets +# +# 2021-01-19, Robin Mueller +# - Add CODE_COVERAGE_VERBOSE option which will allow to print out commands which are run +# - Added the option for users to set the GCOVR_ADDITIONAL_ARGS variable to supply additional +# flags to the gcovr command +# +# 2020-05-04, Mihchael Davis +# - Add -fprofile-abs-path to make gcno files contain absolute paths +# - Fix BASE_DIRECTORY not working when defined +# - Change BYPRODUCT from folder to index.html to stop ninja from complaining about double defines +# +# 2021-05-10, Martin Stump +# - Check if the generator is multi-config before warning about non-Debug builds +# +# 2022-02-22, Marko Wehle +# - Change gcovr output from -o <filename> for --xml <filename> and --html <filename> output respectively. +# This will allow for Multiple Output Formats at the same time by making use of GCOVR_ADDITIONAL_ARGS, e.g. GCOVR_ADDITIONAL_ARGS "--txt". +# +# 2022-09-28, Sebastian Mueller +# - fix append_coverage_compiler_flags_to_target to correctly add flags +# - replace "-fprofile-arcs -ftest-coverage" with "--coverage" (equivalent) +# +# USAGE: +# +# 1. Copy this file into your cmake modules path. +# +# 2. Add the following line to your CMakeLists.txt (best inside an if-condition +# using a CMake option() to enable it just optionally): +# include(CodeCoverage) +# +# 3. Append necessary compiler flags for all supported source files: +# append_coverage_compiler_flags() +# Or for specific target: +# append_coverage_compiler_flags_to_target(YOUR_TARGET_NAME) +# +# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og +# +# 4. If you need to exclude additional directories from the report, specify them +# using full paths in the COVERAGE_EXCLUDES variable before calling +# setup_target_for_coverage_*(). +# Example: +# set(COVERAGE_EXCLUDES +# '${PROJECT_SOURCE_DIR}/src/dir1/*' +# '/path/to/my/src/dir2/*') +# Or, use the EXCLUDE argument to setup_target_for_coverage_*(). +# Example: +# setup_target_for_coverage_lcov( +# NAME coverage +# EXECUTABLE testrunner +# EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*") +# +# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set +# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR) +# Example: +# set(COVERAGE_EXCLUDES "dir1/*") +# setup_target_for_coverage_gcovr_html( +# NAME coverage +# EXECUTABLE testrunner +# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src" +# EXCLUDE "dir2/*") +# +# 5. Use the functions described below to create a custom make target which +# runs your test executable and produces a code coverage report. +# +# 6. Build a Debug build: +# cmake -DCMAKE_BUILD_TYPE=Debug .. +# make +# make my_coverage_target +# + +include(CMakeParseArguments) + +option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE) + +# Check prereqs +find_program( GCOV_PATH gcov ) +find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) +find_program( FASTCOV_PATH NAMES fastcov fastcov.py ) +find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) +find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) +find_program( CPPFILT_PATH NAMES c++filt ) + +if(NOT GCOV_PATH) + message(FATAL_ERROR "gcov not found! Aborting...") +endif() # NOT GCOV_PATH + +# Check supported compiler (Clang, GNU and Flang) +get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +foreach(LANG ${LANGUAGES}) + if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3) + message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") + endif() + elseif(NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU" + AND NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(LLVM)?[Ff]lang") + message(FATAL_ERROR "Compiler is not GNU or Flang! Aborting...") + endif() +endforeach() + +set(COVERAGE_COMPILER_FLAGS "-g --coverage" + CACHE INTERNAL "") +if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag(-fprofile-abs-path HAVE_fprofile_abs_path) + if(HAVE_fprofile_abs_path) + set(COVERAGE_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + endif() +endif() + +set(CMAKE_Fortran_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the Fortran compiler during coverage builds." + FORCE ) +set(CMAKE_CXX_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C++ compiler during coverage builds." + FORCE ) +set(CMAKE_C_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C compiler during coverage builds." + FORCE ) +set(CMAKE_EXE_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used for linking binaries during coverage builds." + FORCE ) +set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used by the shared libraries linker during coverage builds." + FORCE ) +mark_as_advanced( + CMAKE_Fortran_FLAGS_COVERAGE + CMAKE_CXX_FLAGS_COVERAGE + CMAKE_C_FLAGS_COVERAGE + CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) + +get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)) + message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") +endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) + +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + link_libraries(gcov) +endif() + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_lcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# ) +function(setup_target_for_coverage_lcov) + + set(options NO_DEMANGLE SONARQUBE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() # NOT LCOV_PATH + + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() # NOT GENHTML_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(LCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND LCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES LCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Setting up commands which will be run to generate coverage data. + # Cleanup lcov + set(LCOV_CLEAN_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory . + -b ${BASEDIR} --zerocounters + ) + # Create baseline to make sure untouched files show up in the report + set(LCOV_BASELINE_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b + ${BASEDIR} -o ${Coverage_NAME}.base + ) + # Run tests + set(LCOV_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Capturing lcov counters and generating report + set(LCOV_CAPTURE_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b + ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture + ) + # add baseline counters + set(LCOV_BASELINE_COUNT_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base + -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total + ) + # filter collected data to final coverage report + set(LCOV_FILTER_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove + ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info + ) + # Generate HTML output + set(LCOV_GEN_HTML_CMD + ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o + ${Coverage_NAME} ${Coverage_NAME}.info + ) + if(${Coverage_SONARQUBE}) + # Generate SonarQube output + set(GCOVR_XML_CMD + ${GCOVR_PATH} --sonarqube ${Coverage_NAME}_sonarqube.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + set(GCOVR_XML_CMD_COMMAND + COMMAND ${GCOVR_XML_CMD} + ) + set(GCOVR_XML_CMD_BYPRODUCTS ${Coverage_NAME}_sonarqube.xml) + set(GCOVR_XML_CMD_COMMENT COMMENT "SonarQube code coverage info report saved in ${Coverage_NAME}_sonarqube.xml.") + endif() + + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + message(STATUS "Command to clean up lcov: ") + string(REPLACE ";" " " LCOV_CLEAN_CMD_SPACED "${LCOV_CLEAN_CMD}") + message(STATUS "${LCOV_CLEAN_CMD_SPACED}") + + message(STATUS "Command to create baseline: ") + string(REPLACE ";" " " LCOV_BASELINE_CMD_SPACED "${LCOV_BASELINE_CMD}") + message(STATUS "${LCOV_BASELINE_CMD_SPACED}") + + message(STATUS "Command to run the tests: ") + string(REPLACE ";" " " LCOV_EXEC_TESTS_CMD_SPACED "${LCOV_EXEC_TESTS_CMD}") + message(STATUS "${LCOV_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to capture counters and generate report: ") + string(REPLACE ";" " " LCOV_CAPTURE_CMD_SPACED "${LCOV_CAPTURE_CMD}") + message(STATUS "${LCOV_CAPTURE_CMD_SPACED}") + + message(STATUS "Command to add baseline counters: ") + string(REPLACE ";" " " LCOV_BASELINE_COUNT_CMD_SPACED "${LCOV_BASELINE_COUNT_CMD}") + message(STATUS "${LCOV_BASELINE_COUNT_CMD_SPACED}") + + message(STATUS "Command to filter collected data: ") + string(REPLACE ";" " " LCOV_FILTER_CMD_SPACED "${LCOV_FILTER_CMD}") + message(STATUS "${LCOV_FILTER_CMD_SPACED}") + + message(STATUS "Command to generate lcov HTML output: ") + string(REPLACE ";" " " LCOV_GEN_HTML_CMD_SPACED "${LCOV_GEN_HTML_CMD}") + message(STATUS "${LCOV_GEN_HTML_CMD_SPACED}") + + if(${Coverage_SONARQUBE}) + message(STATUS "Command to generate SonarQube XML output: ") + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") + message(STATUS "${GCOVR_XML_CMD_SPACED}") + endif() + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + COMMAND ${LCOV_CLEAN_CMD} + COMMAND ${LCOV_BASELINE_CMD} + COMMAND ${LCOV_EXEC_TESTS_CMD} + COMMAND ${LCOV_CAPTURE_CMD} + COMMAND ${LCOV_BASELINE_COUNT_CMD} + COMMAND ${LCOV_FILTER_CMD} + COMMAND ${LCOV_GEN_HTML_CMD} + ${GCOVR_XML_CMD_COMMAND} + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.base + ${Coverage_NAME}.capture + ${Coverage_NAME}.total + ${Coverage_NAME}.info + ${GCOVR_XML_CMD_BYPRODUCTS} + ${Coverage_NAME}/index.html + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." + ) + + # Show where to find the lcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." + ${GCOVR_XML_CMD_COMMENT} + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_lcov + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_xml( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the +# GCVOR command. +function(setup_target_for_coverage_gcovr_xml) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + # Set up commands which will be run to generate coverage data + # Run tests + set(GCOVR_XML_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Running gcovr + set(GCOVR_XML_CMD + ${GCOVR_PATH} --xml ${Coverage_NAME}.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + + message(STATUS "Command to run tests: ") + string(REPLACE ";" " " GCOVR_XML_EXEC_TESTS_CMD_SPACED "${GCOVR_XML_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_XML_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to generate gcovr XML coverage data: ") + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") + message(STATUS "${GCOVR_XML_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_XML_EXEC_TESTS_CMD} + COMMAND ${GCOVR_XML_CMD} + + BYPRODUCTS ${Coverage_NAME}.xml + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce Cobertura code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." + ) +endfunction() # setup_target_for_coverage_gcovr_xml + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_html( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the +# GCVOR command. +function(setup_target_for_coverage_gcovr_html) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + # Set up commands which will be run to generate coverage data + # Run tests + set(GCOVR_HTML_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Create folder + set(GCOVR_HTML_FOLDER_CMD + ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME} + ) + # Running gcovr + set(GCOVR_HTML_CMD + ${GCOVR_PATH} --html ${Coverage_NAME}/index.html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + + message(STATUS "Command to run tests: ") + string(REPLACE ";" " " GCOVR_HTML_EXEC_TESTS_CMD_SPACED "${GCOVR_HTML_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_HTML_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to create a folder: ") + string(REPLACE ";" " " GCOVR_HTML_FOLDER_CMD_SPACED "${GCOVR_HTML_FOLDER_CMD}") + message(STATUS "${GCOVR_HTML_FOLDER_CMD_SPACED}") + + message(STATUS "Command to generate gcovr HTML coverage data: ") + string(REPLACE ";" " " GCOVR_HTML_CMD_SPACED "${GCOVR_HTML_CMD}") + message(STATUS "${GCOVR_HTML_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD} + COMMAND ${GCOVR_HTML_FOLDER_CMD} + COMMAND ${GCOVR_HTML_CMD} + + BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html # report directory + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce HTML code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_gcovr_html + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_fastcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/" "src/dir2/" # Patterns to exclude. +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# SKIP_HTML # Don't create html report +# POST_CMD perl -i -pe s!${PROJECT_SOURCE_DIR}/!!g ctest_coverage.json # E.g. for stripping source dir from file paths +# ) +function(setup_target_for_coverage_fastcov) + + set(options NO_DEMANGLE SKIP_HTML) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES FASTCOV_ARGS GENHTML_ARGS POST_CMD) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT FASTCOV_PATH) + message(FATAL_ERROR "fastcov not found! Aborting...") + endif() + + if(NOT Coverage_SKIP_HTML AND NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (Patterns, not paths, for fastcov) + set(FASTCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_FASTCOV_EXCLUDES}) + list(APPEND FASTCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES FASTCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Set up commands which will be run to generate coverage data + set(FASTCOV_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}) + + set(FASTCOV_CAPTURE_CMD ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + --search-directory ${BASEDIR} + --process-gcno + --output ${Coverage_NAME}.json + --exclude ${FASTCOV_EXCLUDES} + ) + + set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH} + -C ${Coverage_NAME}.json --lcov --output ${Coverage_NAME}.info + ) + + if(Coverage_SKIP_HTML) + set(FASTCOV_HTML_CMD ";") + else() + set(FASTCOV_HTML_CMD ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} + -o ${Coverage_NAME} ${Coverage_NAME}.info + ) + endif() + + set(FASTCOV_POST_CMD ";") + if(Coverage_POST_CMD) + set(FASTCOV_POST_CMD ${Coverage_POST_CMD}) + endif() + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Code coverage commands for target ${Coverage_NAME} (fastcov):") + + message(" Running tests:") + string(REPLACE ";" " " FASTCOV_EXEC_TESTS_CMD_SPACED "${FASTCOV_EXEC_TESTS_CMD}") + message(" ${FASTCOV_EXEC_TESTS_CMD_SPACED}") + + message(" Capturing fastcov counters and generating report:") + string(REPLACE ";" " " FASTCOV_CAPTURE_CMD_SPACED "${FASTCOV_CAPTURE_CMD}") + message(" ${FASTCOV_CAPTURE_CMD_SPACED}") + + message(" Converting fastcov .json to lcov .info:") + string(REPLACE ";" " " FASTCOV_CONVERT_CMD_SPACED "${FASTCOV_CONVERT_CMD}") + message(" ${FASTCOV_CONVERT_CMD_SPACED}") + + if(NOT Coverage_SKIP_HTML) + message(" Generating HTML report: ") + string(REPLACE ";" " " FASTCOV_HTML_CMD_SPACED "${FASTCOV_HTML_CMD}") + message(" ${FASTCOV_HTML_CMD_SPACED}") + endif() + if(Coverage_POST_CMD) + message(" Running post command: ") + string(REPLACE ";" " " FASTCOV_POST_CMD_SPACED "${FASTCOV_POST_CMD}") + message(" ${FASTCOV_POST_CMD_SPACED}") + endif() + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + + # Cleanup fastcov + COMMAND ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + --search-directory ${BASEDIR} + --zerocounters + + COMMAND ${FASTCOV_EXEC_TESTS_CMD} + COMMAND ${FASTCOV_CAPTURE_CMD} + COMMAND ${FASTCOV_CONVERT_CMD} + COMMAND ${FASTCOV_HTML_CMD} + COMMAND ${FASTCOV_POST_CMD} + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.info + ${Coverage_NAME}.json + ${Coverage_NAME}/index.html # report directory + + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report." + ) + + set(INFO_MSG "fastcov code coverage info report saved in ${Coverage_NAME}.info and ${Coverage_NAME}.json.") + if(NOT Coverage_SKIP_HTML) + string(APPEND INFO_MSG " Open ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html in your browser to view the coverage report.") + endif() + # Show where to find the fastcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo ${INFO_MSG} + ) + +endfunction() # setup_target_for_coverage_fastcov + +function(append_coverage_compiler_flags) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") +endfunction() # append_coverage_compiler_flags + +# Setup coverage for specific library +function(append_coverage_compiler_flags_to_target name) + separate_arguments(_flag_list NATIVE_COMMAND "${COVERAGE_COMPILER_FLAGS}") + target_compile_options(${name} PRIVATE ${_flag_list}) + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + target_link_libraries(${name} PRIVATE gcov) + endif() +endfunction() diff --git a/aidge_core/aidge_export_aidge/static/cmake/PybindModuleCreation.cmake b/aidge_core/aidge_export_aidge/static/cmake/PybindModuleCreation.cmake new file mode 100644 index 0000000000000000000000000000000000000000..87e70fc38c9e4ec4ddb44cbe5d7fb2a31c2e94d6 --- /dev/null +++ b/aidge_core/aidge_export_aidge/static/cmake/PybindModuleCreation.cmake @@ -0,0 +1,21 @@ +function(generate_python_binding name target_to_bind) + add_definitions(-DPYBIND) + Include(FetchContent) + + FetchContent_Declare( + PyBind11 + GIT_REPOSITORY https://github.com/pybind/pybind11.git + GIT_TAG v2.10.4 # or a later release + ) + + # Use the New FindPython mode, recommanded. Requires CMake 3.15+ + find_package(Python COMPONENTS Interpreter Development) + FetchContent_MakeAvailable(PyBind11) + + message(STATUS "Creating binding for module ${name}") + file(GLOB_RECURSE pybind_src_files "python_binding/*.cpp") + + pybind11_add_module(${name} MODULE ${pybind_src_files} "NO_EXTRAS") # NO EXTRA recquired for pip install + target_include_directories(${name} PUBLIC "python_binding") + target_link_libraries(${name} PUBLIC ${target_to_bind}) +endfunction() diff --git a/aidge_core/aidge_export_aidge/static/export-config.cmake.in b/aidge_core/aidge_export_aidge/static/export-config.cmake.in new file mode 100644 index 0000000000000000000000000000000000000000..f3604be11c27d86caf1ad8a48b333b9bd8f30625 --- /dev/null +++ b/aidge_core/aidge_export_aidge/static/export-config.cmake.in @@ -0,0 +1,3 @@ +include(${CMAKE_CURRENT_LIST_DIR}/aidge_backend_cpu-config-version.cmake) + +include(${CMAKE_CURRENT_LIST_DIR}/aidge_backend_cpu-targets.cmake) diff --git a/aidge_core/aidge_export_aidge/static/include/dnn.hpp b/aidge_core/aidge_export_aidge/static/include/dnn.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3a4d5c02eceee1054c93b8ad635a71d3d04ac2fc --- /dev/null +++ b/aidge_core/aidge_export_aidge/static/include/dnn.hpp @@ -0,0 +1,17 @@ +#ifndef DNN_HPP +#define DNN_HPP +#include <aidge/graph/GraphView.hpp> +/** + * @brief This file contains all of what is related to the construction of the + * neural network + * + */ + +/** + * @brief This function generate the exported Aidge::GraphView. + * + * @return std::shared_ptr<Aidge::GraphView> + */ +std::shared_ptr<Aidge::GraphView> generateModel(); + +#endif /* DNN_HPP */ diff --git a/aidge_core/aidge_export_aidge/static/main.cpp b/aidge_core/aidge_export_aidge/static/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ab8bac1851b6d2dae4bf97bd3af10e19e0b71c1e --- /dev/null +++ b/aidge_core/aidge_export_aidge/static/main.cpp @@ -0,0 +1,16 @@ +#include <iostream> +#include <aidge/backend/cpu.hpp> + +#include "include/dnn.hpp" + +int main() +{ + + std::cout << "BEGIN" << std::endl; + + std::shared_ptr<Aidge::GraphView> graph = generateModel(); + + std::cout << "END" << std::endl; + + return 0; +} diff --git a/aidge_core/aidge_export_aidge/static/project_name.txt b/aidge_core/aidge_export_aidge/static/project_name.txt new file mode 100644 index 0000000000000000000000000000000000000000..d5637593fe045bb602bd181bf0f242f0943fb9fd --- /dev/null +++ b/aidge_core/aidge_export_aidge/static/project_name.txt @@ -0,0 +1 @@ +export diff --git a/aidge_core/aidge_export_aidge/static/python_binding/pybind.cpp b/aidge_core/aidge_export_aidge/static/python_binding/pybind.cpp new file mode 100644 index 0000000000000000000000000000000000000000..072fc4cd6012996a4fcda1d18b8244209c69797a --- /dev/null +++ b/aidge_core/aidge_export_aidge/static/python_binding/pybind.cpp @@ -0,0 +1,14 @@ +#include <pybind11/pybind11.h> + +#include "dnn.hpp" + +namespace py = pybind11; + + +void init_export(py::module& m){ + m.def("generate_model", generateModel); +} + +PYBIND11_MODULE(export, m) { + init_export(m); +} diff --git a/aidge_core/aidge_export_aidge/static/version.txt b/aidge_core/aidge_export_aidge/static/version.txt new file mode 100644 index 0000000000000000000000000000000000000000..77d6f4ca23711533e724789a0a0045eab28c5ea6 --- /dev/null +++ b/aidge_core/aidge_export_aidge/static/version.txt @@ -0,0 +1 @@ +0.0.0 diff --git a/aidge_core/aidge_export_aidge/templates/attributes/conv.jinja b/aidge_core/aidge_export_aidge/templates/attributes/conv.jinja new file mode 100644 index 0000000000000000000000000000000000000000..88b976a47fdeaad587e9ec50c27a085e88de23c5 --- /dev/null +++ b/aidge_core/aidge_export_aidge/templates/attributes/conv.jinja @@ -0,0 +1,19 @@ +#ifndef EXPORT_ATTRIBUTES_{{name|upper}}_H +#define EXPORT_ATTRIBUTES_{{name|upper}}_H + +#define _{{name|upper}}_IN_CHANNELS {{InChannels}} +#define _{{name|upper}}_OUT_CHANNELS {{OutChannels}} + +{% for i in range(KernelDims|length) %} +#define _{{name|upper}}_KERNEL_{{i}} {{KernelDims[i]}} +{%- endfor %} +{% for i in range(StrideDims|length) %} +#define _{{name|upper}}_STRIDE_{{i}} {{StrideDims[i]}} +{%- endfor %} +{% for i in range(DilationDims|length) %} +#define _{{name|upper}}_DILATION_{{i}} {{DilationDims[i]}} +{%- endfor %} + +#define _{{name|upper}}_NO_BIAS {{NoBias|int}} + +#endif /* EXPORT_ATTRIBUTES_{{name|upper}}_H */ diff --git a/aidge_core/aidge_export_aidge/templates/attributes/fc.jinja b/aidge_core/aidge_export_aidge/templates/attributes/fc.jinja new file mode 100644 index 0000000000000000000000000000000000000000..96eabf09d47a9b72eeac8e95ce8f5eb8e6d30243 --- /dev/null +++ b/aidge_core/aidge_export_aidge/templates/attributes/fc.jinja @@ -0,0 +1,9 @@ +#ifndef EXPORT_ATTRIBUTES_{{name|upper}}_H +#define EXPORT_ATTRIBUTES_{{name|upper}}_H + +#define _{{name|upper}}_IN_CHANNELS {{InChannels}} +#define _{{name|upper}}_OUT_CHANNELS {{OutChannels}} + +#define _{{name|upper}}_NO_BIAS {{NoBias|int}} + +#endif /* EXPORT_ATTRIBUTES_{{name|upper}}_H */ diff --git a/aidge_core/aidge_export_aidge/templates/attributes/maxpooling.jinja b/aidge_core/aidge_export_aidge/templates/attributes/maxpooling.jinja new file mode 100644 index 0000000000000000000000000000000000000000..d258f580e6ff9c523a87b834fdccf2f3b14fb133 --- /dev/null +++ b/aidge_core/aidge_export_aidge/templates/attributes/maxpooling.jinja @@ -0,0 +1,13 @@ +#ifndef EXPORT_ATTRIBUTES_{{name|upper}}_H +#define EXPORT_ATTRIBUTES_{{name|upper}}_H + +{% for i in range(KernelDims|length) %} +#define _{{name|upper}}_KERNEL_{{i}} {{KernelDims[i]}} +{%- endfor %} +{% for i in range(StrideDims|length) %} +#define _{{name|upper}}_STRIDE_{{i}} {{StrideDims[i]}} +{%- endfor %} + +#define _{{name|upper}}_CEIL_MODE {{CeilMode|int}} + +#endif /* EXPORT_ATTRIBUTES_{{name|upper}}_H */ diff --git a/aidge_core/aidge_export_aidge/templates/dnn.jinja b/aidge_core/aidge_export_aidge/templates/dnn.jinja new file mode 100644 index 0000000000000000000000000000000000000000..5da46b2d8a439a359dfb1c7ec8ebc18e8d516767 --- /dev/null +++ b/aidge_core/aidge_export_aidge/templates/dnn.jinja @@ -0,0 +1,36 @@ +/******************************************************************************** + * This file has been generated by the Aidge export. + ********************************************************************************/ + +/*** STD INCLUDES ***/ +#include <memory> // std::shared_ptr + +/*** AIDGE INCLUDES ***/ +#include <aidge/graph/GraphView.hpp> // Aidge::GraphView +#include <aidge/graph/Node.hpp> // Aidge::Node +#include <aidge/graph/OpArgs.hpp> // Aidge::Sequential + +/*** AIDGE OPERATORS ***/ +{%- for operator in operators %} +#include <aidge/operator/{{operator}}.hpp> +{%- endfor %} + +/*** OPERATOR ATTRIBUTES & PARAMETERS ***/ +{%- for header in headers %} +#include "{{ header }}" +{%- endfor %} + +/*** HEADER ***/ +#include "dnn.hpp" + + +std::shared_ptr<Aidge::GraphView> generateModel() { + /*** BUILDING GRAPH ***/ + std::shared_ptr<Aidge::GraphView> graph = std::make_shared<Aidge::GraphView>(); + + {%- for action in actions %} + {{ action }} + {%- endfor %} + + return graph; +} diff --git a/aidge_core/aidge_export_aidge/templates/graph_ctor/_set_input.jinja b/aidge_core/aidge_export_aidge/templates/graph_ctor/_set_input.jinja new file mode 100644 index 0000000000000000000000000000000000000000..4bf2ea08c2186014580747757be71c03938d3af3 --- /dev/null +++ b/aidge_core/aidge_export_aidge/templates/graph_ctor/_set_input.jinja @@ -0,0 +1,3 @@ +{%- for input in inputs if input[0] %} +{{input[0]}}->addChild({{name}}, {{input[1]}}, {{loop.index - 1}}); {# NOTE: loop.index begin at 1 #} +{%- endfor %} diff --git a/aidge_core/aidge_export_aidge/templates/graph_ctor/conv.jinja b/aidge_core/aidge_export_aidge/templates/graph_ctor/conv.jinja new file mode 100644 index 0000000000000000000000000000000000000000..72a12c6015e3962eaecc0301f54bf228c16fb29c --- /dev/null +++ b/aidge_core/aidge_export_aidge/templates/graph_ctor/conv.jinja @@ -0,0 +1,27 @@ +{% filter indent(width=4, first=False) %} +/*** {{name|upper}} ***/ +std::shared_ptr<Aidge::Node> {{name}} = + Aidge::Conv( + _{{name|upper}}_IN_CHANNELS, + _{{name|upper}}_OUT_CHANNELS, + { + {%- for i in range(KernelDims|length) -%} + _{{name|upper}}_KERNEL_{{i}}{%- if not loop.last %}, {% endif -%} + {%- endfor -%} + }, + "{{name}}", + { + {%- for i in range(StrideDims|length) -%} + _{{name|upper}}_STRIDE_{{i}} {%- if not loop.last %}, {% endif -%} + {%- endfor -%} + }, + { + {%- for i in range(DilationDims|length) -%} + _{{name|upper}}_DILATION_{{i}} {%- if not loop.last %}, {% endif -%} + {%- endfor -%} + }, + _{{name|upper}}_NO_BIAS + ); +{% include "./_set_input.jinja" %} +graph->add({{name}}); +{% endfilter %} diff --git a/aidge_core/aidge_export_aidge/templates/graph_ctor/fc.jinja b/aidge_core/aidge_export_aidge/templates/graph_ctor/fc.jinja new file mode 100644 index 0000000000000000000000000000000000000000..80d978543fd15d4cce3b4329a9bd3481fb88afaa --- /dev/null +++ b/aidge_core/aidge_export_aidge/templates/graph_ctor/fc.jinja @@ -0,0 +1,12 @@ +{% filter indent(width=4, first=False) %} +/*** {{name|upper}} ***/ +std::shared_ptr<Aidge::Node> {{name}} = + Aidge::FC( + _{{name|upper}}_IN_CHANNELS, + _{{name|upper}}_OUT_CHANNELS, + _{{name|upper}}_NO_BIAS, + "{{name}}" + ); +{% include "./_set_input.jinja" %} +graph->add({{name}}); +{% endfilter %} diff --git a/aidge_core/aidge_export_aidge/templates/graph_ctor/maxpooling.jinja b/aidge_core/aidge_export_aidge/templates/graph_ctor/maxpooling.jinja new file mode 100644 index 0000000000000000000000000000000000000000..c6587c128509712e1a8e903e7484476548e9347d --- /dev/null +++ b/aidge_core/aidge_export_aidge/templates/graph_ctor/maxpooling.jinja @@ -0,0 +1,20 @@ +{% filter indent(width=4, first=False) %} +/*** {{name|upper}} ***/ +std::shared_ptr<Aidge::Node> {{name}} = + Aidge::MaxPooling( + { + {%- for i in range(KernelDims|length) -%} + _{{name|upper}}_KERNEL_{{i}}{%- if not loop.last %}, {% endif -%} + {%- endfor -%} + }, + "{{name}}", + { + {%- for i in range(StrideDims|length) -%} + _{{name|upper}}_STRIDE_{{i}} {%- if not loop.last %}, {% endif -%} + {%- endfor -%} + }, + _{{name|upper}}_CEIL_MODE + ); +{% include "./_set_input.jinja" %} +graph->add({{name}}); +{% endfilter %} diff --git a/aidge_core/aidge_export_aidge/templates/graph_ctor/producer.jinja b/aidge_core/aidge_export_aidge/templates/graph_ctor/producer.jinja new file mode 100644 index 0000000000000000000000000000000000000000..8e0a465a044ebfcc249206f9f5886c7a38fc3252 --- /dev/null +++ b/aidge_core/aidge_export_aidge/templates/graph_ctor/producer.jinja @@ -0,0 +1,9 @@ +{% filter indent(width=4, first=False) %} +/*** {{name|upper}} ***/ +std::shared_ptr<Aidge::Node> {{name}} = + Aidge::Producer( + {{tensor_name}}, + "{{name}}" + ); +graph->add({{name}}); +{% endfilter %} diff --git a/aidge_core/aidge_export_aidge/templates/graph_ctor/relu.jinja b/aidge_core/aidge_export_aidge/templates/graph_ctor/relu.jinja new file mode 100644 index 0000000000000000000000000000000000000000..8fd58a30bddd39647dd3b25b2982e67174220381 --- /dev/null +++ b/aidge_core/aidge_export_aidge/templates/graph_ctor/relu.jinja @@ -0,0 +1,9 @@ +{% filter indent(width=4, first=False) %} +/*** {{name|upper}} ***/ +std::shared_ptr<Aidge::Node> {{name}} = + Aidge::ReLU( + "{{name}}" + ); +{% include "./_set_input.jinja" %} +graph->add({{name}}); +{% endfilter %} diff --git a/aidge_core/aidge_export_aidge/templates/parameter.jinja b/aidge_core/aidge_export_aidge/templates/parameter.jinja new file mode 100644 index 0000000000000000000000000000000000000000..11a407cc89f72f24167871a594decc6d90ab489d --- /dev/null +++ b/aidge_core/aidge_export_aidge/templates/parameter.jinja @@ -0,0 +1,11 @@ +#ifndef EXPORT_PARAMETERS_{{name|upper}}_H +#define EXPORT_PARAMETERS_{{name|upper}}_H + +#include <aidge/data/Tensor.hpp> +#include <memory> + +std::shared_ptr<Aidge::Tensor> {{name}} = std::make_shared<Aidge::Tensor>(Aidge::Array{{dims|length}}D<{{data_t}}, {{ dims|join(", ") }}> { +{{ values }} +}); + +#endif /* EXPORT_PARAMETERS_{{name|upper}}_H */ diff --git a/aidge_core/aidge_export_aidge/utils/__init__.py b/aidge_core/aidge_export_aidge/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..ecdf2aec2692a48e108d5f4ad05ed05803319525 --- /dev/null +++ b/aidge_core/aidge_export_aidge/utils/__init__.py @@ -0,0 +1,11 @@ +from .operator_registry import * + +def parse_node_input(node_inputs: list) -> list: + """Parse node intputs in order to adapt the list for Jinja. + + :param node_inputs: return of node.inputs() + :type node_inputs: list of tuple of aidge_core.Node, output idx. + :return: list of tuple of node name, output idx. + :rtype: list + """ + return [None if parent_node is None else (parent_node.name(), outId) for parent_node, outId in node_inputs] diff --git a/aidge_core/aidge_export_aidge/utils/operator_registry.py b/aidge_core/aidge_export_aidge/utils/operator_registry.py new file mode 100644 index 0000000000000000000000000000000000000000..dd6fbaaceeba9c2125b38354eca9cc116acd29b1 --- /dev/null +++ b/aidge_core/aidge_export_aidge/utils/operator_registry.py @@ -0,0 +1,18 @@ +OPERATORS_REGISTRY = {} + +def operator_register(*args): + + key_list = [arg for arg in args] + + def decorator(operator): + def wrapper(*args, **kwargs): + return operator(*args, **kwargs) + + for key in key_list: + OPERATORS_REGISTRY[key] = operator + + return wrapper + return decorator + +def supported_operators(): + return list(OPERATORS_REGISTRY.keys())