From 41a05be8cbb22e6d2aa7b2da5d58b61ae9f15d51 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 18 Feb 2025 11:28:54 +0100
Subject: [PATCH 1/9] [Feat](Exports) Add a copy_folder function and allow the
 copies to be done through simlinks

---
 aidge_core/export_utils/__init__.py         |  2 +-
 aidge_core/export_utils/code_generation.py  | 23 +++++++++++++++++++--
 aidge_core/export_utils/export_registry.py  |  3 +++
 aidge_core/export_utils/scheduler_export.py |  8 +++++--
 4 files changed, 31 insertions(+), 5 deletions(-)

diff --git a/aidge_core/export_utils/__init__.py b/aidge_core/export_utils/__init__.py
index 72472eee0..a14fc63b1 100644
--- a/aidge_core/export_utils/__init__.py
+++ b/aidge_core/export_utils/__init__.py
@@ -1,5 +1,5 @@
 from .node_export import ExportNode, ExportNodeCpp
-from .code_generation import generate_file, generate_str, copy_file
+from .code_generation import generate_file, generate_str, copy_file, copy_folder
 from .export_registry import ExportLib
 from .scheduler_export import scheduler_export
 from .tensor_export import tensor_to_c, generate_input_file
diff --git a/aidge_core/export_utils/code_generation.py b/aidge_core/export_utils/code_generation.py
index 4f0f4634d..42ae19f79 100644
--- a/aidge_core/export_utils/code_generation.py
+++ b/aidge_core/export_utils/code_generation.py
@@ -44,10 +44,29 @@ def generate_str(template_path: Union[Path, str], **kwargs) -> str:
     return Environment(loader=FileSystemLoader(
         template_path.parent), undefined=StrictUndefined, keep_trailing_newline=True).get_template(template_path.name).render(kwargs)
 
-def copy_file(filename, dst_folder):
+def copy_file(filename, dst_folder, symlink=False):
+    """Copy the given file into the given dst path
+    The symlink arg allows to make a symbolic link instead of copying the file.
+    """
 
     # If directory doesn't exist, create it
     if not os.path.exists(dst_folder):
         os.makedirs(dst_folder)
 
-    shutil.copy(filename, dst_folder)
+    if symlink:
+        dst_folder += "/" + os.path.basename(filename)
+        if not os.path.exists(dst_folder):
+            os.symlink(filename, dst_folder)
+    else:
+        shutil.copy(filename, dst_folder)
+
+def copy_folder(foldername, dst_folder, symlink=False):
+    """Copy the given folder into the given dst path
+    The symlink arg allows to make a symbolic link instead of copying the file.
+    """
+
+    if symlink:
+        os.symlink(foldername, dst_folder)
+    else:
+        shutil.copytree(foldername, dst_folder, dirs_exist_ok=True)
+    
\ No newline at end of file
diff --git a/aidge_core/export_utils/export_registry.py b/aidge_core/export_utils/export_registry.py
index 8927ae516..01329e6a5 100644
--- a/aidge_core/export_utils/export_registry.py
+++ b/aidge_core/export_utils/export_registry.py
@@ -40,6 +40,9 @@ class ExportLib(aidge_core.OperatorImpl):
     # key: Path where static file is
     # Value: Path where to copy the file relative to the export root
     static_files: Dict[str, str] = {}
+    # key: Path where static folder is
+    # Value: Path where to copy the folder relative to the export root
+    static_folders: Dict[str, str] = {}
     # Main memory section
     mem_section = None
     # Custom forward generation jinja file
diff --git a/aidge_core/export_utils/scheduler_export.py b/aidge_core/export_utils/scheduler_export.py
index 8aaedc18d..aaca6b76a 100644
--- a/aidge_core/export_utils/scheduler_export.py
+++ b/aidge_core/export_utils/scheduler_export.py
@@ -2,7 +2,7 @@ import aidge_core
 import os
 import shutil
 from pathlib import Path
-from aidge_core.export_utils import ExportLib, generate_file, copy_file
+from aidge_core.export_utils import ExportLib, generate_file, copy_file, copy_folder
 from typing import List, Tuple
 
 
@@ -208,4 +208,8 @@ def scheduler_export(scheduler, export_folder_path: str, export_lib: ExportLib =
     if export_lib is not None:
         # Copy all static files in the export
         for source, destination in export_lib.static_files.items():
-            copy_file(source, str(export_folder / destination))
+            copy_file(source, str(export_folder / destination), test_mode)
+            
+        # Copy all static folders in the export
+        for source, destination in export_lib.static_folders.items():
+            copy_folder(source, str(export_folder / destination), test_mode)
-- 
GitLab


From afaa5157ba50a3c11c170b07685679f1b981c7fa Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Wed, 5 Mar 2025 13:34:06 +0100
Subject: [PATCH 2/9] [Fix] copy_folder() function now create the parent
 directory before creating the symlink

---
 aidge_core/export_utils/code_generation.py | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/aidge_core/export_utils/code_generation.py b/aidge_core/export_utils/code_generation.py
index 42ae19f79..8b3bf65ed 100644
--- a/aidge_core/export_utils/code_generation.py
+++ b/aidge_core/export_utils/code_generation.py
@@ -44,7 +44,7 @@ def generate_str(template_path: Union[Path, str], **kwargs) -> str:
     return Environment(loader=FileSystemLoader(
         template_path.parent), undefined=StrictUndefined, keep_trailing_newline=True).get_template(template_path.name).render(kwargs)
 
-def copy_file(filename, dst_folder, symlink=False):
+def copy_file(filename: Union[Path, str], dst_folder: Union[Path, str], symlink=False):
     """Copy the given file into the given dst path
     The symlink arg allows to make a symbolic link instead of copying the file.
     """
@@ -60,11 +60,16 @@ def copy_file(filename, dst_folder, symlink=False):
     else:
         shutil.copy(filename, dst_folder)
 
-def copy_folder(foldername, dst_folder, symlink=False):
+def copy_folder(foldername: Union[Path, str], dst_folder: Union[Path, str], symlink=False):
     """Copy the given folder into the given dst path
     The symlink arg allows to make a symbolic link instead of copying the file.
     """
 
+    # If the parent directory doesn't exist, create it
+    parent_dir = Path(dst_folder).parent
+    if not os.path.exists(parent_dir):
+        os.makedirs(parent_dir)
+
     if symlink:
         os.symlink(foldername, dst_folder)
     else:
-- 
GitLab


From 8b3bb72973e03311f6911fc9ce58f1e85e3367f9 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 18 Mar 2025 16:52:54 +0100
Subject: [PATCH 3/9] [Feat](Exports) Add label export in generate_main_cpp()
 function and change the default inputs and labels destination folder from
 "ROOT" to "ROOT/data"

---
 aidge_core/export_utils/generate_main.py      | 29 ++++++++++----
 aidge_core/export_utils/templates/main.jinja  | 38 ++++++++++++++++---
 .../export_utils/templates/main_compare.jinja |  4 +-
 3 files changed, 56 insertions(+), 15 deletions(-)

diff --git a/aidge_core/export_utils/generate_main.py b/aidge_core/export_utils/generate_main.py
index 57fc68bca..b8aa14517 100644
--- a/aidge_core/export_utils/generate_main.py
+++ b/aidge_core/export_utils/generate_main.py
@@ -1,8 +1,8 @@
 import aidge_core
 from pathlib import Path
-from aidge_core.export_utils import generate_file, data_conversion
+from aidge_core.export_utils import generate_file, data_conversion, generate_input_file
 
-def generate_main_cpp(export_folder: str, graph_view: aidge_core.GraphView, inputs_tensor=None) -> None:
+def generate_main_cpp(export_folder: str, graph_view: aidge_core.GraphView, inputs_tensor=None, labels=None) -> None:
     """
     Generate a C++ file to manage the forward pass of a model using the given graph structure.
 
@@ -18,7 +18,10 @@ def generate_main_cpp(export_folder: str, graph_view: aidge_core.GraphView, inpu
                        ordered input/output data within the computational graph.
     :type graph_view: aidge_core.graph_view
     :param inputs_tensor: **For future** argument to provide tensor to use in the main function, not implemented yet!
-    :type inputs_tensor: None
+                          By default, the input of the given graph will be exported.
+    :type inputs_tensor: aidge_core.Tensor
+    :param labels: Argument to provide labels tensor to generate and use in the main function. 
+    :type labels: aidge_core.Tensor
     :raises RuntimeError: If there is an inconsistency in the output arguments (names, data types, sizes),
                           indicating an internal bug in the graph representation.
     """
@@ -41,7 +44,18 @@ def generate_main_cpp(export_folder: str, graph_view: aidge_core.GraphView, inpu
             else:
                 aidge_core.Log.notice(f"No input tensor set for {in_name}, main generated will not be functionnal after code generation.")
         else:
-            aidge_core.export_utils.generate_input_file(export_folder=export_folder, array_name=in_name, tensor=input_tensor)
+            # Generate input file
+            generate_input_file(
+                 export_folder=str(Path(export_folder) / "data"), 
+                 array_name=in_name, 
+                 tensor=input_tensor)
+        if labels is not None:
+             # Generate labels
+             generate_input_file(
+                  export_folder=str(Path(export_folder) / "data"),
+                  array_name="labels",
+                  tensor=labels
+             )
 
     for out_node, out_id in gv_outputs:
         outputs_name.append(f"{out_node.name()}_output_{out_id}")
@@ -60,7 +74,8 @@ def generate_main_cpp(export_folder: str, graph_view: aidge_core.GraphView, inpu
         inputs_name=inputs_name,
         outputs_name=outputs_name,
         outputs_dtype=outputs_dtype,
-        outputs_size=outputs_size
+        outputs_size=outputs_size,
+        labels=(labels is not None)
     )
 
 
@@ -103,7 +118,7 @@ def generate_main_compare_cpp(export_folder: str, graph_view: aidge_core.GraphVi
             else:
                 aidge_core.Log.notice(f"No input tensor set for {in_name}, main generated will not be functionnal after code generation.")
         else:
-            aidge_core.export_utils.generate_input_file(export_folder=export_folder, array_name=in_name, tensor=input_tensor)
+            generate_input_file(export_folder=export_folder, array_name=in_name, tensor=input_tensor)
 
     for out_node, out_id in gv_outputs:
         out_name = f"{out_node.name()}_output_{out_id}"
@@ -114,7 +129,7 @@ def generate_main_compare_cpp(export_folder: str, graph_view: aidge_core.GraphVi
         if out_tensor is None or out_tensor.undefined() or not out_tensor.has_impl():
                 aidge_core.Log.notice(f"No input tensor set for {out_name}, main generated will not be functionnal after code generation.")
         else:
-            aidge_core.export_utils.generate_input_file(export_folder=export_folder, array_name=out_name+"_expected", tensor=out_tensor)
+            generate_input_file(export_folder=export_folder, array_name=out_name+"_expected", tensor=out_tensor)
 
     if len(outputs_name) != len(outputs_dtype) or len(outputs_name) != len(outputs_size):
             raise RuntimeError("FATAL: Output args list does not have the same length this is an internal bug.")
diff --git a/aidge_core/export_utils/templates/main.jinja b/aidge_core/export_utils/templates/main.jinja
index 697a97b53..b44f40a90 100644
--- a/aidge_core/export_utils/templates/main.jinja
+++ b/aidge_core/export_utils/templates/main.jinja
@@ -1,11 +1,14 @@
 
 #include <iostream>
 #include "forward.hpp"
-{% for name in inputs_name %}
-#include "{{ name }}.h"
-{% endfor %}
+{%- for name in inputs_name %}
+#include "data/{{ name }}.h"
+{%- endfor %}
+{%- if labels %}
+#include "data/labels.h"
+{%- endif %}
 
-{% set printf_formats = {
+{%- set printf_formats = {
     "double": "%lf",
     "float": "%f",
     "int8_t": "%hhd",
@@ -28,13 +31,36 @@ int main()
     // Call the forward function
     {{ func_name }}({{ inputs_name|join(", ") }}{% if inputs_name %}, {% endif %}&{{ outputs_name|join(", &") }});
 
-    // Print the results of each output
+    // Print the results
+    {%- if labels %}
+    int prediction;
+    int confidence;
+
     {%- for o in range(outputs_name | length) %}
+    prediction = 0;
+    confidence = {{ outputs_name[o] }}[0];
+
+    for (int o = 0; o < {{ outputs_size[0] }}; ++o) {
+        if ({{ outputs_name[0] }}[o] > confidence) {
+            prediction = o;
+            confidence = {{ outputs_name[0] }}[o];
+        }
+    }
+
+    printf("Prediction : %d (%d)\n", prediction, confidence);
+    printf("Label : %d\n", labels[{{ o }}]);
+
+    {%- endfor %}
+    {%- else %}
+    {%- for o in range(outputs_name | length) %}
+
     printf("{{ outputs_name[o] }}:\n");
     for (int o = 0; o < {{ outputs_size[o] }}; ++o) {
         printf("{{ printf_formats[outputs_dtype[o]] }} ", {{ outputs_name[o] }}[o]);
     }
     printf("\n");
-    {% endfor %}
+
+    {%- endfor %}
+    {%- endif %}
     return 0;
 }
diff --git a/aidge_core/export_utils/templates/main_compare.jinja b/aidge_core/export_utils/templates/main_compare.jinja
index 3cc4c986d..7113fb0f2 100644
--- a/aidge_core/export_utils/templates/main_compare.jinja
+++ b/aidge_core/export_utils/templates/main_compare.jinja
@@ -16,12 +16,12 @@
 
 // Inputs
 {% for name in inputs_name %}
-#include "{{ name }}.h"
+#include "data/{{ name }}.h"
 {% endfor %}
 
 // Outputs
 {% for name in outputs_name %}
-#include "{{ name }}_expected.h"
+#include "data/{{ name }}_expected.h"
 {% endfor %}
 
 int main()
-- 
GitLab


From 264e3a21a93a44baed46c24cb269839779102130 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 18 Mar 2025 16:55:32 +0100
Subject: [PATCH 4/9] [Feat](Exports) Add a function to remove the optional
 inputs from the inputs list so they are not exported

---
 aidge_core/export_utils/__init__.py     |  1 +
 aidge_core/export_utils/export_utils.py | 20 ++++++++++++++++++++
 2 files changed, 21 insertions(+)
 create mode 100644 aidge_core/export_utils/export_utils.py

diff --git a/aidge_core/export_utils/__init__.py b/aidge_core/export_utils/__init__.py
index a14fc63b1..9f5214754 100644
--- a/aidge_core/export_utils/__init__.py
+++ b/aidge_core/export_utils/__init__.py
@@ -4,3 +4,4 @@ from .export_registry import ExportLib
 from .scheduler_export import scheduler_export
 from .tensor_export import tensor_to_c, generate_input_file
 from .generate_main import generate_main_cpp, generate_main_compare_cpp
+from .export_utils import remove_optional_inputs
diff --git a/aidge_core/export_utils/export_utils.py b/aidge_core/export_utils/export_utils.py
new file mode 100644
index 000000000..fd6624287
--- /dev/null
+++ b/aidge_core/export_utils/export_utils.py
@@ -0,0 +1,20 @@
+import aidge_core
+
+def remove_optional_inputs(graph_view: aidge_core.GraphView):
+    """ Remove optional inputs from the ordered_list of the model
+
+    There are 3 inputs categories :
+        - 0 : Data
+        - 1 : Parameters
+        - 2 : Optional Inputs
+
+    :param graph_view: An instance of :py:class:`aidge_core.graph_view`, providing access to nodes and
+                       ordered input/output data within the computational graph.
+    :type graph_view: aidge_core.graph_view
+    """
+
+    inputNodes = []
+    for n in graph_view.get_ordered_inputs():
+        if n[0].get_operator().input_category(n[1]) in [aidge_core.InputCategory(0), aidge_core.InputCategory(1)]:
+            inputNodes.append(n)
+    graph_view.set_ordered_inputs(inputNodes)
-- 
GitLab


From 81f9c274bbb14a4c3b9a0126908d942e2f2d8e3e Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 18 Mar 2025 16:57:48 +0100
Subject: [PATCH 5/9] [Chore](Exports) Change from test_mode to dev_mode for
 coherence

---
 aidge_core/export_utils/scheduler_export.py | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/aidge_core/export_utils/scheduler_export.py b/aidge_core/export_utils/scheduler_export.py
index aaca6b76a..a6b590cb3 100644
--- a/aidge_core/export_utils/scheduler_export.py
+++ b/aidge_core/export_utils/scheduler_export.py
@@ -6,7 +6,7 @@ from aidge_core.export_utils import ExportLib, generate_file, copy_file, copy_fo
 from typing import List, Tuple
 
 
-def scheduler_export(scheduler, export_folder_path: str, export_lib: ExportLib = None, memory_manager=None, memory_manager_args=None, test_mode=False) -> None:
+def scheduler_export(scheduler, export_folder_path: str, export_lib: ExportLib = None, memory_manager=None, memory_manager_args=None, dev_mode=False) -> None:
     """Exports an aidge_core.Scheduler to C++ code.
 
     This function generates files for a given computation graph, including forward-pass functions,
@@ -57,8 +57,12 @@ def scheduler_export(scheduler, export_folder_path: str, export_lib: ExportLib =
     :type memory_manager: callable
     :param memory_manager_args: Additional arguments passed to `memory_manager`. Defaults to an empty dictionary.
     :type memory_manager_args: dict, optional
-    :param test_mode: Additional argument which may be used during forward generation.
-    :type test_mode: bool, optional
+    :param dev_mode: Wether or not the developer mode is enabled. If enabled, the export files
+                     will be symlinks from the aidge export module. Therefore, modifying
+                     a file within the export will change the module as well. 
+                     The dev_mode flag is also passed to the forward jinja templates to allow export
+                     customization (ie. Adding a debug mode for instance).
+    :type dev_mode: bool, optional
     """
     graphview = scheduler.graph_view()
     export_folder = Path().absolute() / export_folder_path
@@ -181,7 +185,7 @@ def scheduler_export(scheduler, export_folder_path: str, export_lib: ExportLib =
         inputs_dtype=inputs_dtype,
         outputs_name=outputs_name,
         outputs_dtype=outputs_dtype,
-        test_mode=test_mode,
+        dev_mode=dev_mode,
         list_node_names=list_node_names
     )
 
@@ -199,7 +203,7 @@ def scheduler_export(scheduler, export_folder_path: str, export_lib: ExportLib =
         inputs_dtype=inputs_dtype,
         outputs_name=outputs_name,
         outputs_dtype=outputs_dtype,
-        test_mode=test_mode
+        dev_mode=dev_mode
     )
 
     if len(outputs_name) != len(outputs_dtype) or len(outputs_name) != len(outputs_size):
@@ -208,8 +212,8 @@ def scheduler_export(scheduler, export_folder_path: str, export_lib: ExportLib =
     if export_lib is not None:
         # Copy all static files in the export
         for source, destination in export_lib.static_files.items():
-            copy_file(source, str(export_folder / destination), test_mode)
+            copy_file(source, str(export_folder / destination), dev_mode)
             
         # Copy all static folders in the export
         for source, destination in export_lib.static_folders.items():
-            copy_folder(source, str(export_folder / destination), test_mode)
+            copy_folder(source, str(export_folder / destination), dev_mode)
-- 
GitLab


From 58890a40db9f4fe711d2875f869094c434fa3916 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Tue, 18 Mar 2025 16:58:40 +0100
Subject: [PATCH 6/9] [Chore](Exports) Add access to data_conversion functions

---
 aidge_core/export_utils/__init__.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/aidge_core/export_utils/__init__.py b/aidge_core/export_utils/__init__.py
index 9f5214754..ddd810515 100644
--- a/aidge_core/export_utils/__init__.py
+++ b/aidge_core/export_utils/__init__.py
@@ -4,4 +4,5 @@ from .export_registry import ExportLib
 from .scheduler_export import scheduler_export
 from .tensor_export import tensor_to_c, generate_input_file
 from .generate_main import generate_main_cpp, generate_main_compare_cpp
+from .data_conversion import aidge2c, aidge2export_type
 from .export_utils import remove_optional_inputs
-- 
GitLab


From 3ea64436468acc4b5a95b71c92726f85d89dc081 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 21 Mar 2025 10:42:01 +0100
Subject: [PATCH 7/9] [Fix](Exports) labels are now generted once and optional
 inputs are ignored

---
 aidge_core/export_utils/generate_main.py | 35 ++++++++++++++----------
 1 file changed, 20 insertions(+), 15 deletions(-)

diff --git a/aidge_core/export_utils/generate_main.py b/aidge_core/export_utils/generate_main.py
index b8aa14517..ae86116dc 100644
--- a/aidge_core/export_utils/generate_main.py
+++ b/aidge_core/export_utils/generate_main.py
@@ -32,30 +32,35 @@ def generate_main_cpp(export_folder: str, graph_view: aidge_core.GraphView, inpu
     gv_inputs: list[tuple[aidge_core.Node, int]] = graph_view.get_ordered_inputs()
     gv_outputs: list[tuple[aidge_core.Node, int]] = graph_view.get_ordered_outputs()
 
+    # Generate input file(s)
     for in_node, in_idx in gv_inputs:
         in_node_input, in_node_input_idx = in_node.input(in_idx)
         in_name = f"{in_node.name()}_input_{in_idx}" if in_node_input is None else f"{in_node_input.name()}_output_{in_node_input_idx}"
-        inputs_name.append(in_name)
         input_tensor = in_node.get_operator().get_input(in_idx)
-        if input_tensor is None or input_tensor.undefined() or not input_tensor.has_impl():
-            if inputs_tensor is not None:
-                aidge_core.Log.notice("No support for inputs_tensor argument yet.")
-                aidge_core.Log.notice(f"No input tensor set for {in_name}, main generated will not be functionnal after code generation.")
-            else:
-                aidge_core.Log.notice(f"No input tensor set for {in_name}, main generated will not be functionnal after code generation.")
+        # if input_tensor is None or input_tensor.undefined() or not input_tensor.has_impl():
+        #     if inputs_tensor is not None:
+        #         aidge_core.Log.notice("No support for inputs_tensor argument yet.")
+        #         aidge_core.Log.notice(f"No input tensor set for {in_name}, main generated will not be functionnal after code generation.")
+        #     else:
+        #         aidge_core.Log.notice(f"No input tensor set for {in_name}, main generated will not be functionnal after code generation.")
+
+        # Ignore optional inputs
+        if in_node.get_operator().input_category(in_idx) == aidge_core.InputCategory(2):
+             aidge_core.Log.notice(f"Ignoring optional input {in_name}.")
         else:
-            # Generate input file
+            inputs_name.append(in_name)
             generate_input_file(
                  export_folder=str(Path(export_folder) / "data"), 
                  array_name=in_name, 
                  tensor=input_tensor)
-        if labels is not None:
-             # Generate labels
-             generate_input_file(
-                  export_folder=str(Path(export_folder) / "data"),
-                  array_name="labels",
-                  tensor=labels
-             )
+    
+    # Generate labels file
+    if labels is not None:
+        generate_input_file(
+            export_folder=str(Path(export_folder) / "data"),
+            array_name="labels",
+            tensor=labels
+        )
 
     for out_node, out_id in gv_outputs:
         outputs_name.append(f"{out_node.name()}_output_{out_id}")
-- 
GitLab


From a0f56bb73cc4137fac8e4e98cbe9feb87987c469 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 21 Mar 2025 10:43:10 +0100
Subject: [PATCH 8/9] [Feat](Exports) Change the kernels_to_copy system

The old system had some limitations :
- Not possible to chose a destination path different from the source path
- The copied kernel would be automatically included in the fwd file, sometime leading to unused includes
---
 aidge_core/export_utils/node_export.py | 61 +++++++++++++++++++++-----
 1 file changed, 49 insertions(+), 12 deletions(-)

diff --git a/aidge_core/export_utils/node_export.py b/aidge_core/export_utils/node_export.py
index c24727adf..3247a7ffb 100644
--- a/aidge_core/export_utils/node_export.py
+++ b/aidge_core/export_utils/node_export.py
@@ -364,9 +364,9 @@ class ExportNodeCpp(ExportNode):
     :var include_list: List of include paths (e.g., "include/toto.hpp") to be added to
         the generated export files. Must be defined before export; raises an error if undefined.
     :vartype include_list: list[str]
-    :var kernels_to_copy: List of paths to kernel files that should be copied during
-        export. The kernels are copied to ``kernels_path``, and are automatically
-        added to the include list.
+    :var kernels_to_copy: A list of dict holding src and dst kernels paths to copy in the export.
+        export. The kernels are copied in dst_path (default : self.kernels_path).
+        They are automatically added to the include list unless the fwd_include option is set to False.
     :vartype kernels_to_copy: list[str]
     :var kernels_path: Path where all kernels are stored in the export, prefixed by the
         `export_root`. Defaults to "include/kernels".
@@ -377,6 +377,12 @@ class ExportNodeCpp(ExportNode):
     :var config_extension: File extension for the configuration files, typically for header
         files. Defaults to "h".
     :vartype config_extension: str
+    :var dev_mode: Wether or not the developer mode is enabled. If enabled, the export files
+        will be symlinks from the aidge export module. Therefore, modifying
+        a file within the export will change the module as well. 
+        The dev_mode flag is also passed to the forward jinja templates to allow export
+        customization (ie. Adding a debug mode for instance).
+    :vartype dev_mode: bool
     """
 
     # Path to the template defining how to export the node definition
@@ -385,16 +391,40 @@ class ExportNodeCpp(ExportNode):
     forward_template: str = None
     # List of includes to add example "include/toto.hpp"
     include_list: list = None
-    # A list of path of kernels to copy in the export
-    # kernels are copied in str(export_folder / "include" / "kernels")
-    # They are automatically added to the include list.
-    kernels_to_copy: list = None
+    # A list of dict holding src and dst kernels paths to copy in the export.
+    # kernels are copied in dst_path (default : self.kernels_path)
+    # They are automatically added to the include list unless the fwd_include option is set to False.
+    kernels_to_copy: list[dict] = None
     # Path where all the kernels are stored in the export (prefixed by export_root)
     kernels_path: str = "include/kernels"
     # Path of config folders
     config_path: str = "include/layers"
     # Config_folder_extension
     config_extension: str = "h"
+    # Dev mode - Symlink copy
+    dev_mode: bool = False
+
+
+    def add_kernel_to_copy(self, kernel_src_path: str, kernel_dst_path: str = kernels_path, fwd_include: bool = True):
+        """ Add a kernel to the kernels_to_copy list of dict.
+        
+        :param kernel_src_path: File path for the kernel to copy within the export module.
+        :type kernel_src_path: str
+        :param kernel_dst_path: File path for the kernel to copy within the generated export. 
+        :type kernel_dst_path: str
+        :param fwd_include: Wether the kernel is included in the generated forward file or not.
+        :type fwd_include: bool
+        """
+
+        if self.kernels_to_copy is None:
+            self.kernels_to_copy = []
+
+        kernel_to_copy: dict = {
+            "src_path": kernel_src_path,
+            "dst_path": kernel_dst_path,
+            "fwd_include": fwd_include
+        }
+        self.kernels_to_copy.append(kernel_to_copy)
 
 
     def export(self, export_folder: str):
@@ -419,13 +449,20 @@ class ExportNodeCpp(ExportNode):
 
         kernel_include_list = []
         for kernel in self.kernels_to_copy:
-            kernel_path = Path(kernel)
+
+            # Copy the kernel file
+            kernel_src_path = Path(kernel["src_path"])
+            kernel_dst_path = Path(kernel["dst_path"])
             code_generation.copy_file(
-                kernel_path,
-                str(export_folder / self.kernels_path)
+                kernel_src_path,
+                str(export_folder / kernel_dst_path),
+                self.dev_mode
             )
-            kernel_include_list.append(
-                self.kernels_path + "/" + kernel_path.stem + kernel_path.suffix)
+
+            # Include the kernel file within the fwd
+            if kernel["fwd_include"]:
+                kernel_include_list.append(
+                    kernel_dst_path / (kernel_src_path.stem + kernel_src_path.suffix))
 
         if self.config_template != "":
             path_to_definition = f"{self.config_path}/{self.attributes['name']}.{self.config_extension}"
-- 
GitLab


From a64e9da8cbd2378a21fc46fb60f4520d9036be26 Mon Sep 17 00:00:00 2001
From: Axel Farrugia <axel.farrugia@cea.fr>
Date: Fri, 21 Mar 2025 10:46:03 +0100
Subject: [PATCH 9/9] [Chore](Exports) Add SAVE_OUTPUTS and AIDGE_CMP flags on
 top of fwd file

---
 aidge_core/export_utils/templates/forward.jinja | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/aidge_core/export_utils/templates/forward.jinja b/aidge_core/export_utils/templates/forward.jinja
index fde4b2a13..e054c3489 100644
--- a/aidge_core/export_utils/templates/forward.jinja
+++ b/aidge_core/export_utils/templates/forward.jinja
@@ -1,6 +1,9 @@
 
 #include <stdint.h>
 
+#define SAVE_OUTPUTS false  // Save the feature maps into files (Not compatible with every export)
+#define AIDGE_CMP    false  // Compare export and aidge feature maps (Not compatible with every export)
+
 #ifdef SAVE_OUTPUTS
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -30,7 +33,6 @@ void {{ func_name }} (
     {{ outputs_dtype[o] }}** {{ outputs_name[o] }}_ptr{% if not loop.last %}, {% endif %}
     {%- endfor -%})
 {
-
     {%- for action in actions %}
     {{ action }}
     {%- endfor %}
-- 
GitLab