From c508cf7503fd97e616c12e54f5975a7238ab5987 Mon Sep 17 00:00:00 2001
From: Tristan Laurent <tristan.laurent@cs-soprasteria.com>
Date: Fri, 14 Mar 2025 19:31:11 +0100
Subject: [PATCH 1/2] COMP: QGIS descriptor generator now takes a list of
 application

---
 Modules/Core/Convolution/CMakeLists.txt       |   2 +-
 Modules/Core/Wrappers/QGIS/src/CMakeLists.txt |  33 +-
 .../Wrappers/QGIS/src/otbQgisDescriptor.cxx   | 606 +++++++++---------
 3 files changed, 340 insertions(+), 301 deletions(-)

diff --git a/Modules/Core/Convolution/CMakeLists.txt b/Modules/Core/Convolution/CMakeLists.txt
index 262a67e778..23143bcc31 100644
--- a/Modules/Core/Convolution/CMakeLists.txt
+++ b/Modules/Core/Convolution/CMakeLists.txt
@@ -22,7 +22,7 @@ project(OTBConvolution)
 
 option(OTB_USE_FFTW "Download and compile FFTW third party (license change to GPLv2)" OFF)
 if (OTB_USE_FFTW)
-  message(NOTICE "/!\\ /!\\ /!\\ You are actually compiling OTB with FFTW. As FFTW is distributed under GNU GPLv2, OTB is now provided with GNU GPLv2 license /!\\ /!\\ /!\\ ")
+  message(AUTHOR_WARNING "/!\\ /!\\ /!\\ You are actually compiling OTB with FFTW. As FFTW is distributed under GNU GPLv2, OTB is now provided with GNU GPLv2 license /!\\ /!\\ /!\\ ")
 else()
   message(STATUS "Compiling with FFTW disabled, otbOverlapSaveConvolutionImageFilter will not be available")
 endif()
diff --git a/Modules/Core/Wrappers/QGIS/src/CMakeLists.txt b/Modules/Core/Wrappers/QGIS/src/CMakeLists.txt
index 5dcf0ceffd..d7f7bc375b 100644
--- a/Modules/Core/Wrappers/QGIS/src/CMakeLists.txt
+++ b/Modules/Core/Wrappers/QGIS/src/CMakeLists.txt
@@ -42,17 +42,32 @@ set(generate_descriptor_cmd "$<TARGET_FILE:otbQgisDescriptor>")
 if(CMAKE_SKIP_RPATH AND "${CMAKE_SYSTEM_NAME}"  MATCHES "Linux")
   set(generate_descriptor_cmd "env;LD_LIBRARY_PATH=${OTB_BINARY_DIR}/lib;$<TARGET_FILE:otbQgisDescriptor>")
 endif()
+# generate dependency list
+# put all app into a file
+# same for dfiles
+set(QGIS_APP_LIST_FILE "${CMAKE_CURRENT_BINARY_DIR}/qgis_apps_list.txt")
+file(TOUCH "${QGIS_APP_LIST_FILE}")
+set(QGIS_APP_DESCRIPTOR_PATH "${OTB_BINARY_DIR}/${OTB_INSTALL_DATA_DIR}/description")
 foreach(app_name ${app_names})
   add_dependencies(otbQgisDescriptor otbapp_${app_name})
+  file(APPEND "${QGIS_APP_LIST_FILE}" "${app_name}\n")
   set(dfile "${OTB_BINARY_DIR}/${OTB_INSTALL_DATA_DIR}/description/${app_name}.txt")
-  add_custom_command(OUTPUT "${dfile}"
-    COMMAND ${generate_descriptor_cmd}
-    "${app_name}" "${app_location}" "./${OTB_INSTALL_DATA_DIR}/description/"
-    WORKING_DIRECTORY ${OTB_BINARY_DIR}
-    COMMENT "./bin/otbQgisDescriptor ${app_name} ${app_location} ./${OTB_INSTALL_DATA_DIR}/description/"
-    VERBATIM)
-  list(APPEND dfiles "${dfile}")
+  # add_custom_command(OUTPUT "${dfile}"
+  #   COMMAND ${generate_descriptor_cmd}
+  #   "${app_name}" "${app_location}" "./${OTB_INSTALL_DATA_DIR}/description/"
+  #   WORKING_DIRECTORY ${OTB_BINARY_DIR}
+  #   COMMENT "./bin/otbQgisDescriptor ${app_name} ${app_location} ./${OTB_INSTALL_DATA_DIR}/description/"
+  #   VERBATIM)
 endforeach()
 
-add_custom_target(generate_descriptors DEPENDS ${dfiles} )
-add_dependencies(${otb-module}-all generate_descriptors)
+# call otbQgisDescriptor with the app list, the location to create the files 
+# add_custom_command(OUTPUT "${QGIS_APP_DESCRIPTOR_PATH}/algs.txt"
+#   COMMAND ${generate_descriptor_cmd}
+#   "${CMAKE_CURRENT_BINARY_DIR}/qgis_apps.txt" "${app_location}" "./${OTB_INSTALL_DATA_DIR}/description/"
+#   WORKING_DIRECTORY ${OTB_BINARY_DIR}
+#   COMMENT "Generate gqis descriptor files"
+#   VERBATIM)
+
+
+# add_custom_target(generate_descriptors DEPENDS "${QGIS_APP_DESCRIPTOR_PATH}/algs.txt")
+# add_dependencies(${otb-module}-all generate_descriptors)
diff --git a/Modules/Core/Wrappers/QGIS/src/otbQgisDescriptor.cxx b/Modules/Core/Wrappers/QGIS/src/otbQgisDescriptor.cxx
index 83fa26d184..1579bd317e 100644
--- a/Modules/Core/Wrappers/QGIS/src/otbQgisDescriptor.cxx
+++ b/Modules/Core/Wrappers/QGIS/src/otbQgisDescriptor.cxx
@@ -28,339 +28,363 @@
 #include <iostream>
 #include <cassert>
 #include <fstream>
-
-int main(int argc, char* argv[])
+#include <regex>
+
+#define QGIS_DEBUG 0
+
+using namespace otb::Wrapper;
+static const std::unordered_map<ParameterType, std::string> parameterTypeToString {
+  {ParameterType_Bool, "QgsProcessingParameterBoolean"},
+  {ParameterType_Int, "QgsProcessingParameterNumber"},
+  {ParameterType_Float, "QgsProcessingParameterNumber"},
+  {ParameterType_Double, "QgsProcessingParameterNumber"},
+  {ParameterType_RAM, "QgsProcessingParameterNumber"},
+  {ParameterType_Radius, "QgsProcessingParameterNumber"},
+  {ParameterType_Choice, "OTBParameterChoice"},
+  {ParameterType_String, "QgsProcessingParameterString"},
+  {ParameterType_InputImage, "QgsProcessingParameterRasterLayer"},
+  {ParameterType_InputFilename, "QgsProcessingParameterFile"},
+  {ParameterType_InputImageList, "QgsProcessingParameterMultipleLayers"},
+  {ParameterType_InputVectorData, "QgsProcessingParameterVectorLayer"},
+  {ParameterType_InputFilenameList, "QgsProcessingParameterMultipleLayers"},
+  {ParameterType_InputVectorDataList, "QgsProcessingParameterMultipleLayers"},
+  {ParameterType_OutputImage, "QgsProcessingParameterRasterDestination"},
+  {ParameterType_OutputVectorData, "QgsProcessingParameterVectorDestination"},
+  {ParameterType_OutputFilename, "QgsProcessingParameterFileDestination"},
+  {ParameterType_Directory, "QgsProcessingParameterFile"},
+  {ParameterType_Field, "QgsProcessingParameterField"},
+  {ParameterType_Band, "QgsProcessingParameterBand"},
+  {ParameterType_StringList, "QgsProcessingParameterString"},
+  {ParameterType_ListView, "QgsProcessingParameterEnum"}
+};
+
+bool addModuleToAlgorithmList(std::ofstream& algs_list_file,
+                              const std::string& module,
+                              const std::string& group)
 {
-  if (argc < 3)
-  {
-    std::cerr << "Usage : " << argv[0] << " name OTB_APPLICATION_PATH [out_dir]" << std::endl;
-    return EXIT_FAILURE;
+  if (algs_list_file) {
+    algs_list_file << group << "|" << module << std::endl;
+    return algs_list_file.good();
+  }
+  else {
+    return false;
   }
+}
 
-  using namespace otb::Wrapper;
+std::string getMandatoryOutputImageParam(const Application::Pointer app,
+                                         const std::vector<std::string>& app_keys) {
+  for (const std::string& app_key: app_keys)
+  {
+    Parameter::Pointer param = app->GetParameterByKey(app_key);
+    if (param->GetMandatory() && param->GetType() == ParameterType_OutputImage)
+    {
+      return app_key;
+    }
+  }
+  return ""; // if nothing found
+}
 
-  const std::string module(argv[1]);
+std::string QGISLineFactory(const Application::Pointer app,
+                            const std::string& paramKey)
+{
+  std::ostringstream qgis_line;
+  const Parameter::Pointer param         (app->GetParameterByKey(paramKey));
+  ParameterType            type          (param->GetType());
+  const std::string        description   (app->GetParameterName(paramKey));
+  std::string              optional      ("True");
+  std::string              default_value ("None");
 
-/* TestApplication is removed in CMakeLists.txt */
-#if 0
-  if (module == "TestApplication")
-    return EXIT_SUCCESS;
-#endif
 
-  ApplicationRegistry::AddApplicationPath(argv[2]);
-  Application::Pointer appli = ApplicationRegistry::CreateApplicationFaster(module);
-
-  assert(!appli.IsNull());
+  if (type == ParameterType_Group || type == ParameterType_RAM || param->GetRole() == Role_Output)
+  {
+    // group parameter cannot have any value.
+    // ram is added by qgis-otb processing provider plugin as an advanced parameter for all apps
+    // parameter role cannot be of type Role_Output
+    return "";
+  }
 
-  const std::string group = appli->GetDocTags().size() > 0 ? appli->GetDocTags()[0] : "";
-  assert(!group.empty());
+  // if there is no choice value for a list view parameter
+  // we switch to parameter stringlist to avoid aving empty combobox in QGIS
+  if (type == ParameterType_ListView && app->GetChoiceNames(paramKey).empty()) {
+    type = ParameterType_StringList;
+  }
 
-  std::map<ParameterType, std::string> parameterTypeToString;
-  parameterTypeToString[ParameterType_Bool]                = "QgsProcessingParameterBoolean";
-  parameterTypeToString[ParameterType_Int]                 = "QgsProcessingParameterNumber";
-  parameterTypeToString[ParameterType_Float]               = "QgsProcessingParameterNumber";
-  parameterTypeToString[ParameterType_Double]              = "QgsProcessingParameterNumber";
-  parameterTypeToString[ParameterType_RAM]                 = "QgsProcessingParameterNumber";
-  parameterTypeToString[ParameterType_Radius]              = "QgsProcessingParameterNumber";
-  parameterTypeToString[ParameterType_Choice]              = "OTBParameterChoice";
-  parameterTypeToString[ParameterType_String]              = "QgsProcessingParameterString";
-  parameterTypeToString[ParameterType_InputImage]          = "QgsProcessingParameterRasterLayer";
-  parameterTypeToString[ParameterType_InputFilename]       = "QgsProcessingParameterFile";
-  parameterTypeToString[ParameterType_InputImageList]      = "QgsProcessingParameterMultipleLayers";
-  parameterTypeToString[ParameterType_InputVectorData]     = "QgsProcessingParameterVectorLayer";
-  parameterTypeToString[ParameterType_InputFilenameList]   = "QgsProcessingParameterMultipleLayers";
-  parameterTypeToString[ParameterType_InputVectorDataList] = "QgsProcessingParameterMultipleLayers";
-  parameterTypeToString[ParameterType_OutputImage]         = "QgsProcessingParameterRasterDestination";
-  parameterTypeToString[ParameterType_OutputVectorData]    = "QgsProcessingParameterVectorDestination";
-  parameterTypeToString[ParameterType_OutputFilename]      = "QgsProcessingParameterFileDestination";
-  parameterTypeToString[ParameterType_Directory]           = "QgsProcessingParameterFile";
-  parameterTypeToString[ParameterType_Field]               = "QgsProcessingParameterField";
-  parameterTypeToString[ParameterType_Band]                = "QgsProcessingParameterBand";
-  // TODO
-  parameterTypeToString[ParameterType_StringList] = "QgsProcessingParameterString";
-
-  parameterTypeToString[ParameterType_ListView] = "QgsProcessingParameterEnum";
-
-  const std::vector<std::string> appKeyList = appli->GetParametersKeys(true);
-  const unsigned int             nbOfParam  = appKeyList.size();
 
-  std::string output_file = module + ".txt";
-  std::string algs_txt    = "algs.txt";
-  if (argc > 3)
+  auto it = parameterTypeToString.find(type);
+  if (it == parameterTypeToString.end())
   {
-    output_file = std::string(argv[3]) + module + ".txt";
-    algs_txt    = std::string(argv[3]) + "algs.txt";
+    // NOTE get also module name
+    throw std::runtime_error("No mapping found for parameter '" + paramKey +
+                             "' type=" + parameterTypesStrings[type]);
   }
-  std::ofstream dFile;
-  dFile.open(output_file, std::ios::out);
+  
+  std::string qgis_type = it->second;
 
-  std::string output_parameter_name;
-  bool        hasRasterOutput = false;
+  if (param->GetMandatory())
   {
-    for (unsigned int i = 0; i < nbOfParam; i++)
-    {
-      Parameter::Pointer param = appli->GetParameterByKey(appKeyList[i]);
-      if (param->GetMandatory())
-      {
-        if (appli->GetParameterType(appKeyList[i]) == ParameterType_OutputImage)
-        {
-          output_parameter_name = appKeyList[i];
-          hasRasterOutput       = true;
-        }
-      }
-    }
+    // TODO: avoid workaround for stringlist types (fix appengine)
+    // type == ParameterType_StringList check is needed because:
+    // If parameter is  mandatory it can have no value
+    // It is accepted in OTB that, string list could be generated dynamically
+    // qgis has no such option to handle dynamic values yet..
+    // So mandatory parameters whose type is StringList is considered optional
+    optional = param->HasValue() || type == ParameterType_StringList ? "True" : "False";
   }
 
-  if (output_parameter_name.empty())
-    dFile << module << std::endl;
-  else
-    dFile << module << "|" << output_parameter_name << std::endl;
-
-  dFile << appli->GetDescription() << std::endl;
-  dFile << group << std::endl;
 
-  /*
-  From here onwards each line appended to dFile is passed to QgsProcessingParameter* class constructor.
-  dFile is nothing but a csv (with a .txt) with "|" as delimiter character.
-  First field is the name of Qgis parameter class and rest of it are extracted as list and passed to class constructor.
-  Parsing and calling of parameter classes are done by python.
-  source available : qgis/python/plugins/processing/core/parameters.py
-  source code of qgis parameter is available at: qgis/src/core/processing/qgsprocessingparameters.cpp
-  */
+  bool isEpsgCode = false;
 
-  for (unsigned int i = 0; i < nbOfParam; i++)
+  // use QgsProcessingParameterCrs if required.
+  // CHeck if paramKey ==epsg only or epsg is one onf the extension
+  const std::regex epsg_regex("*\.epsg\.*", std::regex_constants::grep);
+  std::cmatch tmp_match; // useless param just for function
+  if (paramKey == "epsg" || std::regex_match(paramKey, tmp_match, epsg_regex))
   {
-    const std::string        name        = appKeyList[i];
-    const Parameter::Pointer param       = appli->GetParameterByKey(name);
-    ParameterType            type        = appli->GetParameterType(name);
-    const std::string        description = appli->GetParameterName(name);
-
-    // if there is no choice value for a list view parameter
-    // we switch to parameter stringlist to avoid aving empty combobox in QGIS
-    if (type == ParameterType_ListView)
-    {
-      if ( appli->GetChoiceNames(name).empty() )
-      {
-        type = ParameterType_StringList;
-      }
-    }
+    qgis_type  = "QgsProcessingParameterCrs";
+    isEpsgCode = true;
+  }
+
+  qgis_line << qgis_type << "|" << paramKey << "|" << description;
 
-    std::string optional;
-    if (param->GetMandatory())
+  std::vector<std::string>  name_list;
+  std::vector<std::string>  key_list;
+  std::string values;
+  switch (type)
+  {
+  case ParameterType_Int:
+    if (isEpsgCode)
     {
-      // TODO: avoid workaround for stringlist types (fix appengine)
-      // type == ParameterType_StringList check is needed because:
-      // If parameter is  mandatory it can have no value
-      // It is accepted in OTB that, string list could be generated dynamically
-      // qgis has no such option to handle dynamic values yet..
-      // So mandatory parameters whose type is StringList is considered optional
-      optional = param->HasValue() || type == ParameterType_StringList ? "True" : "False";
+      if (param->HasValue() && app->GetParameterInt(paramKey) < 1)
+        default_value = "EPSG: " + app->GetParameterAsString(paramKey);
+      else
+        default_value = "ProjectCrs";
     }
     else
     {
-      optional = "True";
+      qgis_line << "|QgsProcessingParameterNumber.Integer";
+      default_value = param->HasValue() ? app->GetParameterAsString(paramKey) : "0";
     }
-
-    if (type == ParameterType_Group || type == ParameterType_RAM || param->GetRole() == Role_Output)
+    break;
+  case ParameterType_Float:
+  case ParameterType_Double:
+    qgis_line << "|QgsProcessingParameterNumber.Double";
+    default_value = param->HasValue() ? app->GetParameterAsString(paramKey) : "0";
+    break;
+  case ParameterType_Radius:
+    qgis_line << "|QgsProcessingParameterNumber.Integer";
+    default_value = param->HasValue() ? app->GetParameterAsString(paramKey) : "0";
+    break;
+  case ParameterType_InputFilename:
+    // TODO: if parameter InputFilename can give supported extensions
+    // we can use it gitlab #1559
+    qgis_line << "|QgsProcessingParameterFile.File|None";
+    break;
+  case ParameterType_Directory:
+    qgis_line << "|QgsProcessingParameterFile.Folder|False";
+    break;
+  case ParameterType_InputImageList:
+    qgis_line << "|3"; // QgsProcessing.TypeRaster
+    break;
+  case ParameterType_InputVectorDataList:
+  case ParameterType_InputVectorData:
+  case ParameterType_OutputVectorData:
+    qgis_line << "|-1"; // QgsProcessing.TypeVectorAnyGeometry
+    break;
+  case ParameterType_InputFilenameList:
+    qgis_line << "|4"; // QgsProcessing.TypeFile"
+    break;
+  case ParameterType_String:
+    // Below line is interpreted in qgis processing as
+    // 1. default_value = None
+    // 2. multiLine = False
+    // For more details,
+    // please refer to documentation of QgsProcessingParameterString.
+    default_value = "None|False";
+    break;
+  case ParameterType_StringList:
+    // Below line is interpreted in qgis processing as
+    // 1. default_value = None
+    // 2. multiLine = True
+    // For more details,
+    // please refer to documentation of QgsProcessingParameterString.
+    // setting default_value this way is an exception for ParameterType_StringList and ParameterType_String
+    default_value = "None|True";
+    break;
+  case ParameterType_InputImage:
+  case ParameterType_OutputImage:
+    // default is None and nothing to add to qgis_line
+    break;
+  case ParameterType_OutputFilename:
+    // fileFilter and defaultValue is None
+    // OTB does not have any implementation for file extension.
+    default_value = "None|None";
+    break;
+  case ParameterType_ListView:
+    ListViewParameter *lv_param = dynamic_cast<ListViewParameter*>(param.GetPointer());
+    name_list  = app->GetChoiceNames(paramKey);
+    for( auto k : name_list)
+      values += k + ";";
+    values.pop_back();
+    qgis_line << "|" << values ;
+    qgis_line << "|" << (lv_param->GetSingleSelection() ? "False" : "True");
+    break;
+  case ParameterType_Bool:
+    default_value = app->GetParameterAsString(paramKey);
+    break;
+  case ParameterType_Choice:
+    key_list = app->GetChoiceKeys(paramKey);
+    for (auto k : key_list)
+      values += k + ";";
+
+    if (!values.empty())
+      values.pop_back();
+
+    qgis_line << "|" << values;
+    ChoiceParameter* choiceParam = dynamic_cast<ChoiceParameter*>(param.GetPointer());
+    default_value           = std::to_string(choiceParam->GetValue());
+    break;
+  case ParameterType_Field:
+    FieldParameter* fieldParam = dynamic_cast<FieldParameter*>(param.GetPointer());
+
+    enum {
+          STRING  = 0b00000001,
+          NUMERIC = 0b00000010
+    };
+
+    int filterType = 0b00000000;
+    for (auto type : fieldParam->GetTypeFilter())
     {
-      // group parameter cannot have any value.
-      // ram is added by qgis-otb processing provider plugin as an advanced parameter for all apps
-      // parameter role cannot be of type Role_Output
-      continue;
+      if (type == OFTString)
+        filterType |= STRING;
+      else if (type == OFTInteger || type == OFTInteger64 || type == OFTReal)
+        filterType |= NUMERIC;
     }
 
-    auto it = parameterTypeToString.find(type);
-    assert(it != parameterTypeToString.end());
-    if (it == parameterTypeToString.end())
-    {
-      std::cerr << "No mapping found for parameter '" << name << "' type=" << type << std::endl;
-      return EXIT_FAILURE;
-    }
-    std::string qgis_type = it->second;
+    qgis_line << "|None|"
+          << fieldParam->GetVectorData()
+          << "|"  << (filterType == STRING  ? "QgsProcessingParameterField.String" :
+                      filterType == NUMERIC ? "QgsProcessingParameterField.Numeric" :
+                      "QgsProcessingParameterField.Any")
+          << "|" << (fieldParam->GetSingleSelection() ? "False" : "True")
+          << "|" << optional;
+    break;
+  case ParameterType_Band:
+    BandParameter* bandParam = dynamic_cast<BandParameter*>(param.GetPointer());
+
+    qgis_line << "|None|"
+          << bandParam->GetRasterData() << "|"
+          << optional
+          << "|" << (bandParam->GetSingleSelection() ? "False" : "True");
+    break;
+  default:
+    throw std::runtime_error("ERROR: default_value is empty for '" + paramKey +
+                             "' type='" + qgis_type  + "'");
+    break;
+  }
 
-    bool isEpsgCode = false;
+#if QGIS_DEBUG
+      std::cerr << paramKey;
+      std::cerr << " mandatory=" << param->GetMandatory();
+      std::cerr << " HasValue=" << param->HasValue();
+      std::cerr << " qgis_type=" << qgis_type;
+      std::cerr << " optional=" << optional << std::endl;
+#endif
 
-    // use QgsProcessingParameterCrs if required.
-    // TODO: do a regex on name to match ==epsg || *\.epsg.\*
-    if (name == "epsg" || name == "map.epsg.code" || name == "mapproj.epsg.code" || name == "mode.epsg.code")
-    {
-      qgis_type  = "QgsProcessingParameterCrs";
-      isEpsgCode = true;
-    }
+  // optional and default value are not in the same order than
+  // other QGis processing parameters for field and band
+  if (type != ParameterType_Field && type != ParameterType_Band)
+  {
+    qgis_line << "|" << default_value << "|" << optional;
+  }
 
-    dFile << qgis_type << "|" << name << "|" << description;
+  return qgis_line.str();
+}
 
-    std::string default_value = "None";
-    if (type == ParameterType_Int)
-    {
-      if (isEpsgCode)
-      {
-        if (param->HasValue() && appli->GetParameterInt(name) < 1)
-          default_value = "EPSG: " + appli->GetParameterAsString(name);
-        else
-          default_value = "ProjectCrs";
-      }
-      else
-      {
-        dFile << "|QgsProcessingParameterNumber.Integer";
-        default_value = param->HasValue() ? appli->GetParameterAsString(name) : "0";
-      }
-    }
-    else if (type == ParameterType_Float || type == ParameterType_Double)
-    {
-      dFile << "|QgsProcessingParameterNumber.Double";
-      default_value = param->HasValue() ? appli->GetParameterAsString(name) : "0";
-    }
-    else if (type == ParameterType_Radius)
-    {
-      dFile << "|QgsProcessingParameterNumber.Integer";
-      default_value = param->HasValue() ? appli->GetParameterAsString(name) : "0";
-    }
-    else if (type == ParameterType_InputFilename)
-    {
-      // TODO: if parameter InputFilename can give supported extensions
-      // we can use it gitlab #1559
-      dFile << "|QgsProcessingParameterFile.File|None";
-    }
-    else if (type == ParameterType_Directory)
-    {
-      dFile << "|QgsProcessingParameterFile.Folder|False";
-    }
-    else if (type == ParameterType_InputImageList)
-    {
-      dFile << "|3"; // QgsProcessing.TypeRaster
-    }
-    else if (type == ParameterType_InputVectorDataList || type == ParameterType_InputVectorData || type == ParameterType_OutputVectorData)
-    {
-      dFile << "|-1"; // QgsProcessing.TypeVectorAnyGeometry
-    }
-    else if (type == ParameterType_InputFilenameList)
-    {
-      dFile << "|4"; // QgsProcessing.TypeFile"
-    }
-    else if (type == ParameterType_String)
-    {
-      // Below line is interpreted in qgis processing as
-      // 1. default_value = None
-      // 2. multiLine = False
-      // For more details,
-      // please refer to documentation of QgsProcessingParameterString.
-      default_value = "None|False";
-    }
-    else if (type == ParameterType_StringList)
-    {
-      // Below line is interpreted in qgis processing as
-      // 1. default_value = None
-      // 2. multiLine = True
-      // For more details,
-      // please refer to documentation of QgsProcessingParameterString.
-      // setting default_value this way is an exception for ParameterType_StringList and ParameterType_String
-      default_value = "None|True";
-    }
-    else if (type == ParameterType_InputImage || type == ParameterType_OutputImage)
-    {
-      // default is None and nothing to add to dFile
-    }
-    else if (type == ParameterType_OutputFilename)
-    {
-      // fileFilter and defaultValue is None
-      // OTB does not have any implementation for file extension.
-      default_value = "None|None";
-    }
-    else if (type == ParameterType_ListView)
-    {
-        ListViewParameter *lv_param = dynamic_cast<ListViewParameter*>(param.GetPointer());
-	      std::vector<std::string>  name_list  = appli->GetChoiceNames(name);
-	      std::string values = "";
-	      for( auto k : name_list)
-          values += k + ";";
-	      values.pop_back();
-	      dFile << "|" << values ;
-        dFile << "|" << (lv_param->GetSingleSelection() ? "False" : "True");
-    }
-    else if (type == ParameterType_Bool)
-    {
-      default_value = appli->GetParameterAsString(name);
-    }
-    else if (type == ParameterType_Choice)
-    {
-      std::vector<std::string> key_list = appli->GetChoiceKeys(name);
-      std::string              values   = "";
-      for (auto k : key_list)
-        values += k + ";";
+bool writeModuleQGISDescriptor(const Application::Pointer app,
+                               const std::string& module_name,
+                               const std::string& group,
+                               const std::string& descriptor_file_path) {
+  // RAII capsule to automatically close file when we are out of scope
+  auto module_descriptor = std::unique_ptr<std::ofstream, std::function<void(std::ofstream*)>>(
+                              new std::ofstream(descriptor_file_path, std::ios::out),
+                              [](std::ofstream* o) { o->close(); }
+                           );
+
+  const std::vector<std::string> appKeyList = app->GetParametersKeys(true);
+
+  // --------------QGIS Module file header-----------------
+  // composed like following (line break included):
+  // <module name>|<mandatory output param name (if exists)>
+  // <description>
+  // <group>
+  const std::string mandatory_out_param_name = getMandatoryOutputImageParam(app, appKeyList);
+
+  // Add app name and mandatory output if it exists
+  if (mandatory_out_param_name.empty())
+    *module_descriptor << module_name << std::endl;
+  else
+    *module_descriptor << module_name << "|" << mandatory_out_param_name << std::endl;
 
-      if (!values.empty())
-        values.pop_back();
+  // add description and group
+  *module_descriptor << app->GetDescription() << std::endl;
+  *module_descriptor << group << std::endl;
 
-      dFile << "|" << values;
-      ChoiceParameter* cparam = dynamic_cast<ChoiceParameter*>(param.GetPointer());
-      default_value           = std::to_string(cparam->GetValue());
-    }
-    else if (type == ParameterType_Field)
-    {
-      FieldParameter *f_param = dynamic_cast<FieldParameter*>(param.GetPointer());
-
-      enum {
-            STRING = 1 << 0,
-            NUMERIC = 1 << 1
-      };
-
-      int filterType = 0;
-      for (auto type : f_param->GetTypeFilter())
-      {
-        if (type == OFTString)
-          filterType |= STRING;
-        else if (type == OFTInteger || type == OFTInteger64 || type == OFTReal)
-          filterType |= NUMERIC;
-      }
-
-      dFile << "|None|"
-            << f_param->GetVectorData()
-            << "|"  << (filterType == STRING  ? "QgsProcessingParameterField.String" :
-                        filterType == NUMERIC ? "QgsProcessingParameterField.Numeric" :
-                        "QgsProcessingParameterField.Any")
-            << "|" << (f_param->GetSingleSelection() ? "False" : "True")
-            << "|" << optional;
-    }
-    else if (type == ParameterType_Band)
-    {
-      BandParameter *f_param = dynamic_cast<BandParameter*>(param.GetPointer());
+  //-------------QGIS Module parameters part----------------
 
-      dFile << "|None|"
-            << f_param->GetRasterData() << "|"
-            << optional
-            << "|" << (f_param->GetSingleSelection() ? "False" : "True");
-    }
-    else
-    {
-      std::cout << "ERROR: default_value is empty for '" << name << "' type='" << qgis_type << "'" << std::endl;
-      return EXIT_FAILURE;
-    }
+  /*
+  From here onwards each line appended to module_descriptor is passed to QgsProcessingParameter* class constructor.
+  module_descriptor is nothing but a csv (with a .txt) with "|" as delimiter character.
+  First field is the name of Qgis parameter class and rest of it are extracted as list and passed to class constructor.
+  Parsing and calling of parameter classes are done by python.
+  source available : qgis/python/plugins/processing/core/parameters.py
+  source code of qgis parameter is available at: qgis/src/core/processing/qgsprocessingparameters.cpp
+  */
+  std::string param_line;
+  for (const std::string& paramKey: appKeyList) {
+    // the following function can throw, but let is thow upper
+    param_line = QGISLineFactory(app, paramKey);
+    if (!param_line.empty())
+      *module_descriptor << param_line << std::endl;
+  }
 
-#if 0
-    	  std::cerr << name;
-    	  std::cerr << " mandatory=" << param->GetMandatory();
-    	  std::cerr << " HasValue=" << param->HasValue();
-    	  std::cerr << " qgis_type=" << qgis_type;
-    	  std::cerr << " optional=" << optional << std::endl;
-#endif
+  if (!mandatory_out_param_name.empty())
+  {
+    *module_descriptor << "*QgsProcessingParameterEnum|outputpixeltype|Output pixel type|uint8;int;float;double|False|2|True" << std::endl;
+  }
+}
 
-    // optional and default value are not in the same order than
-    // other QGis processing parameters for field and band
-    if (type != ParameterType_Field && type != ParameterType_Band)
-    {
-      dFile << "|" << default_value << "|" << optional;
-    }
-    dFile << std::endl;
+
+int main(int argc, char* argv[])
+{
+  if (argc < 3)
+  {
+    std::cerr << "Usage : " << argv[0] << " name OTB_APPLICATION_PATH [out_dir]" << std::endl;
+    return EXIT_FAILURE;
   }
 
-  if (hasRasterOutput)
+  const std::string module(argv[1]);
+
+  ApplicationRegistry::AddApplicationPath(argv[2]);
+  Application::Pointer appli = ApplicationRegistry::CreateApplicationFaster(module);
+
+  assert(!appli.IsNull());
+
+  std::string output_file = module + ".txt";
+  std::string algs_txt    = "algs.txt";
+
+  if (argc > 3)
   {
-    dFile << "*QgsProcessingParameterEnum|outputpixeltype|Output pixel type|uint8;int;float;double|False|2|True" << std::endl;
+    output_file = std::string(argv[3]) + module + ".txt";
+    algs_txt    = std::string(argv[3]) + "algs.txt";
   }
 
+  const std::string group = appli->GetDocTags().size() > 0 ? appli->GetDocTags()[0] : "";
+  assert(!group.empty());
+
+  // write module
+  writeModuleQGISDescriptor(appli, module, group, output_file);
+
   std::cerr << "Writing " << output_file << std::endl;
-  dFile.close();
 
   std::ofstream indexFile;
   indexFile.open(algs_txt, std::ios::out | std::ios::app);
-- 
GitLab


From 7eeded5110f0bf2273bfcd29e84e60c698dee80a Mon Sep 17 00:00:00 2001
From: Tristan Laurent <tristan.laurent@cs-soprasteria.com>
Date: Mon, 17 Mar 2025 16:17:05 +0100
Subject: [PATCH 2/2] COMP: changes on otbQgisDescriptor avoiding concurrent
 access on algs.txt

---
 Modules/Core/Wrappers/QGIS/src/CMakeLists.txt |  27 +--
 .../Wrappers/QGIS/src/otbQgisDescriptor.cxx   | 198 ++++++++++--------
 2 files changed, 125 insertions(+), 100 deletions(-)

diff --git a/Modules/Core/Wrappers/QGIS/src/CMakeLists.txt b/Modules/Core/Wrappers/QGIS/src/CMakeLists.txt
index d7f7bc375b..db199d5964 100644
--- a/Modules/Core/Wrappers/QGIS/src/CMakeLists.txt
+++ b/Modules/Core/Wrappers/QGIS/src/CMakeLists.txt
@@ -46,28 +46,23 @@ endif()
 # put all app into a file
 # same for dfiles
 set(QGIS_APP_LIST_FILE "${CMAKE_CURRENT_BINARY_DIR}/qgis_apps_list.txt")
+# if file does not exists it does not emit an error
+file(REMOVE "${QGIS_APP_LIST_FILE}")
 file(TOUCH "${QGIS_APP_LIST_FILE}")
 set(QGIS_APP_DESCRIPTOR_PATH "${OTB_BINARY_DIR}/${OTB_INSTALL_DATA_DIR}/description")
+
 foreach(app_name ${app_names})
   add_dependencies(otbQgisDescriptor otbapp_${app_name})
   file(APPEND "${QGIS_APP_LIST_FILE}" "${app_name}\n")
-  set(dfile "${OTB_BINARY_DIR}/${OTB_INSTALL_DATA_DIR}/description/${app_name}.txt")
-  # add_custom_command(OUTPUT "${dfile}"
-  #   COMMAND ${generate_descriptor_cmd}
-  #   "${app_name}" "${app_location}" "./${OTB_INSTALL_DATA_DIR}/description/"
-  #   WORKING_DIRECTORY ${OTB_BINARY_DIR}
-  #   COMMENT "./bin/otbQgisDescriptor ${app_name} ${app_location} ./${OTB_INSTALL_DATA_DIR}/description/"
-  #   VERBATIM)
 endforeach()
 
 # call otbQgisDescriptor with the app list, the location to create the files 
-# add_custom_command(OUTPUT "${QGIS_APP_DESCRIPTOR_PATH}/algs.txt"
-#   COMMAND ${generate_descriptor_cmd}
-#   "${CMAKE_CURRENT_BINARY_DIR}/qgis_apps.txt" "${app_location}" "./${OTB_INSTALL_DATA_DIR}/description/"
-#   WORKING_DIRECTORY ${OTB_BINARY_DIR}
-#   COMMENT "Generate gqis descriptor files"
-#   VERBATIM)
-
+add_custom_command(OUTPUT "${QGIS_APP_DESCRIPTOR_PATH}/algs.txt"
+  COMMAND ${generate_descriptor_cmd}
+  "${QGIS_APP_LIST_FILE}" "${app_location}" "./${OTB_INSTALL_DATA_DIR}/description/"
+  WORKING_DIRECTORY ${OTB_BINARY_DIR}
+  COMMENT "Generate gqis descriptor files"
+  VERBATIM)
 
-# add_custom_target(generate_descriptors DEPENDS "${QGIS_APP_DESCRIPTOR_PATH}/algs.txt")
-# add_dependencies(${otb-module}-all generate_descriptors)
+add_custom_target(generate_descriptors DEPENDS "${QGIS_APP_DESCRIPTOR_PATH}/algs.txt")
+add_dependencies(${otb-module}-all generate_descriptors)
diff --git a/Modules/Core/Wrappers/QGIS/src/otbQgisDescriptor.cxx b/Modules/Core/Wrappers/QGIS/src/otbQgisDescriptor.cxx
index 1579bd317e..1baaa8b303 100644
--- a/Modules/Core/Wrappers/QGIS/src/otbQgisDescriptor.cxx
+++ b/Modules/Core/Wrappers/QGIS/src/otbQgisDescriptor.cxx
@@ -33,43 +33,6 @@
 #define QGIS_DEBUG 0
 
 using namespace otb::Wrapper;
-static const std::unordered_map<ParameterType, std::string> parameterTypeToString {
-  {ParameterType_Bool, "QgsProcessingParameterBoolean"},
-  {ParameterType_Int, "QgsProcessingParameterNumber"},
-  {ParameterType_Float, "QgsProcessingParameterNumber"},
-  {ParameterType_Double, "QgsProcessingParameterNumber"},
-  {ParameterType_RAM, "QgsProcessingParameterNumber"},
-  {ParameterType_Radius, "QgsProcessingParameterNumber"},
-  {ParameterType_Choice, "OTBParameterChoice"},
-  {ParameterType_String, "QgsProcessingParameterString"},
-  {ParameterType_InputImage, "QgsProcessingParameterRasterLayer"},
-  {ParameterType_InputFilename, "QgsProcessingParameterFile"},
-  {ParameterType_InputImageList, "QgsProcessingParameterMultipleLayers"},
-  {ParameterType_InputVectorData, "QgsProcessingParameterVectorLayer"},
-  {ParameterType_InputFilenameList, "QgsProcessingParameterMultipleLayers"},
-  {ParameterType_InputVectorDataList, "QgsProcessingParameterMultipleLayers"},
-  {ParameterType_OutputImage, "QgsProcessingParameterRasterDestination"},
-  {ParameterType_OutputVectorData, "QgsProcessingParameterVectorDestination"},
-  {ParameterType_OutputFilename, "QgsProcessingParameterFileDestination"},
-  {ParameterType_Directory, "QgsProcessingParameterFile"},
-  {ParameterType_Field, "QgsProcessingParameterField"},
-  {ParameterType_Band, "QgsProcessingParameterBand"},
-  {ParameterType_StringList, "QgsProcessingParameterString"},
-  {ParameterType_ListView, "QgsProcessingParameterEnum"}
-};
-
-bool addModuleToAlgorithmList(std::ofstream& algs_list_file,
-                              const std::string& module,
-                              const std::string& group)
-{
-  if (algs_list_file) {
-    algs_list_file << group << "|" << module << std::endl;
-    return algs_list_file.good();
-  }
-  else {
-    return false;
-  }
-}
 
 std::string getMandatoryOutputImageParam(const Application::Pointer app,
                                          const std::vector<std::string>& app_keys) {
@@ -87,6 +50,39 @@ std::string getMandatoryOutputImageParam(const Application::Pointer app,
 std::string QGISLineFactory(const Application::Pointer app,
                             const std::string& paramKey)
 {
+  static const std::unordered_map<ParameterType, std::string> parameterTypeToString {
+    {ParameterType_Bool, "QgsProcessingParameterBoolean"},
+    {ParameterType_Int, "QgsProcessingParameterNumber"},
+    {ParameterType_Float, "QgsProcessingParameterNumber"},
+    {ParameterType_Double, "QgsProcessingParameterNumber"},
+    {ParameterType_RAM, "QgsProcessingParameterNumber"},
+    {ParameterType_Radius, "QgsProcessingParameterNumber"},
+    {ParameterType_Choice, "OTBParameterChoice"},
+    {ParameterType_String, "QgsProcessingParameterString"},
+    {ParameterType_InputImage, "QgsProcessingParameterRasterLayer"},
+    {ParameterType_InputFilename, "QgsProcessingParameterFile"},
+    {ParameterType_InputImageList, "QgsProcessingParameterMultipleLayers"},
+    {ParameterType_InputVectorData, "QgsProcessingParameterVectorLayer"},
+    {ParameterType_InputFilenameList, "QgsProcessingParameterMultipleLayers"},
+    {ParameterType_InputVectorDataList, "QgsProcessingParameterMultipleLayers"},
+    {ParameterType_OutputImage, "QgsProcessingParameterRasterDestination"},
+    {ParameterType_OutputVectorData, "QgsProcessingParameterVectorDestination"},
+    {ParameterType_OutputFilename, "QgsProcessingParameterFileDestination"},
+    {ParameterType_Directory, "QgsProcessingParameterFile"},
+    {ParameterType_Field, "QgsProcessingParameterField"},
+    {ParameterType_Band, "QgsProcessingParameterBand"},
+    {ParameterType_StringList, "QgsProcessingParameterString"},
+    {ParameterType_ListView, "QgsProcessingParameterEnum"}
+  };
+
+  enum FilterType {
+    NONE = 0b00000000,
+    STRING = 0b00000001,
+    NUMERIC = 0b00000010,
+    MIXED = 0b00000011
+  };
+
+
   std::ostringstream qgis_line;
   const Parameter::Pointer param         (app->GetParameterByKey(paramKey));
   ParameterType            type          (param->GetType());
@@ -135,20 +131,26 @@ std::string QGISLineFactory(const Application::Pointer app,
   bool isEpsgCode = false;
 
   // use QgsProcessingParameterCrs if required.
-  // CHeck if paramKey ==epsg only or epsg is one onf the extension
-  const std::regex epsg_regex("*\.epsg\.*", std::regex_constants::grep);
-  std::cmatch tmp_match; // useless param just for function
-  if (paramKey == "epsg" || std::regex_match(paramKey, tmp_match, epsg_regex))
-  {
+  // Check if paramKey == "epsg" only or epsg is one of the extensions
+  // use double \ to get one in final expression
+  const std::regex epsg_regex(".+\\.epsg\\..+");
+  if (paramKey == "epsg" || std::regex_match(paramKey, epsg_regex)) {
     qgis_type  = "QgsProcessingParameterCrs";
     isEpsgCode = true;
   }
 
   qgis_line << qgis_type << "|" << paramKey << "|" << description;
 
-  std::vector<std::string>  name_list;
-  std::vector<std::string>  key_list;
+  int filterType;
+  std::vector<std::string> name_list;
+  std::vector<std::string> key_list;
   std::string values;
+  std::string proc_param_field;
+  ListViewParameter *lv_param;
+  ChoiceParameter* choiceParam;
+  FieldParameter* fieldParam;
+  BandParameter* bandParam;
+
   switch (type)
   {
   case ParameterType_Int:
@@ -220,7 +222,7 @@ std::string QGISLineFactory(const Application::Pointer app,
     default_value = "None|None";
     break;
   case ParameterType_ListView:
-    ListViewParameter *lv_param = dynamic_cast<ListViewParameter*>(param.GetPointer());
+    lv_param = dynamic_cast<ListViewParameter*>(param.GetPointer());
     name_list  = app->GetChoiceNames(paramKey);
     for( auto k : name_list)
       values += k + ";";
@@ -240,36 +242,36 @@ std::string QGISLineFactory(const Application::Pointer app,
       values.pop_back();
 
     qgis_line << "|" << values;
-    ChoiceParameter* choiceParam = dynamic_cast<ChoiceParameter*>(param.GetPointer());
+    choiceParam = dynamic_cast<ChoiceParameter*>(param.GetPointer());
     default_value           = std::to_string(choiceParam->GetValue());
     break;
   case ParameterType_Field:
-    FieldParameter* fieldParam = dynamic_cast<FieldParameter*>(param.GetPointer());
+    fieldParam = dynamic_cast<FieldParameter*>(param.GetPointer());
+    filterType = FilterType::NONE;
 
-    enum {
-          STRING  = 0b00000001,
-          NUMERIC = 0b00000010
-    };
-
-    int filterType = 0b00000000;
     for (auto type : fieldParam->GetTypeFilter())
     {
       if (type == OFTString)
-        filterType |= STRING;
+      filterType |= FilterType::STRING;
       else if (type == OFTInteger || type == OFTInteger64 || type == OFTReal)
-        filterType |= NUMERIC;
+      filterType |= FilterType::NUMERIC;
     }
 
+    if (filterType == FilterType::NONE || filterType == FilterType::MIXED)
+      proc_param_field = "QgsProcessingParameterField.Any";
+    else if (filterType == FilterType::STRING)
+      proc_param_field = "QgsProcessingParameterField.String";
+    else if (filterType == FilterType::NUMERIC)
+      proc_param_field = "QgsProcessingParameterField.Numeric";
+
     qgis_line << "|None|"
           << fieldParam->GetVectorData()
-          << "|"  << (filterType == STRING  ? "QgsProcessingParameterField.String" :
-                      filterType == NUMERIC ? "QgsProcessingParameterField.Numeric" :
-                      "QgsProcessingParameterField.Any")
+          << "|" << proc_param_field
           << "|" << (fieldParam->GetSingleSelection() ? "False" : "True")
           << "|" << optional;
     break;
   case ParameterType_Band:
-    BandParameter* bandParam = dynamic_cast<BandParameter*>(param.GetPointer());
+    bandParam = dynamic_cast<BandParameter*>(param.GetPointer());
 
     qgis_line << "|None|"
           << bandParam->GetRasterData() << "|"
@@ -300,11 +302,13 @@ std::string QGISLineFactory(const Application::Pointer app,
   return qgis_line.str();
 }
 
-bool writeModuleQGISDescriptor(const Application::Pointer app,
+void writeModuleQGISDescriptor(const Application::Pointer app,
                                const std::string& module_name,
                                const std::string& group,
                                const std::string& descriptor_file_path) {
   // RAII capsule to automatically close file when we are out of scope
+  // as this function is able to throw, we must ensure that the file is
+  // correctly closed
   auto module_descriptor = std::unique_ptr<std::ofstream, std::function<void(std::ofstream*)>>(
                               new std::ofstream(descriptor_file_path, std::ios::out),
                               [](std::ofstream* o) { o->close(); }
@@ -347,6 +351,8 @@ bool writeModuleQGISDescriptor(const Application::Pointer app,
       *module_descriptor << param_line << std::endl;
   }
 
+  // if there is a mandatory output, add a parameter to specify the type of
+  // output pixels
   if (!mandatory_out_param_name.empty())
   {
     *module_descriptor << "*QgsProcessingParameterEnum|outputpixeltype|Output pixel type|uint8;int;float;double|False|2|True" << std::endl;
@@ -356,43 +362,67 @@ bool writeModuleQGISDescriptor(const Application::Pointer app,
 
 int main(int argc, char* argv[])
 {
+  // ------- Args parsing --------
   if (argc < 3)
   {
-    std::cerr << "Usage : " << argv[0] << " name OTB_APPLICATION_PATH [out_dir]" << std::endl;
+    std::cerr << "Usage : " << argv[0] << " module_list_file OTB_APPLICATION_PATH [out_dir]" << std::endl;
     return EXIT_FAILURE;
   }
 
-  const std::string module(argv[1]);
-
+  const std::string module_list_path(argv[1]);
   ApplicationRegistry::AddApplicationPath(argv[2]);
-  Application::Pointer appli = ApplicationRegistry::CreateApplicationFaster(module);
-
-  assert(!appli.IsNull());
-
-  std::string output_file = module + ".txt";
-  std::string algs_txt    = "algs.txt";
 
+  std::string work_dir("");
+  std::string output_file = module_list_path + ".txt";
   if (argc > 3)
   {
-    output_file = std::string(argv[3]) + module + ".txt";
-    algs_txt    = std::string(argv[3]) + "algs.txt";
+    work_dir = std::string(argv[3]) + "/";
   }
+  // ------------------------
+
+  // get the list of all modules to generate their descriptors
+  std::ifstream module_list_file(module_list_path, std::ios_base::in);
+  const std::string algs_txt    = work_dir + "algs.txt";
+
+  // get the algs_txt that contains modules and their groups
+  std::ofstream algs_list;
+  algs_list.open(algs_txt, std::ios::out | std::ios::app);
+
+  std::string module_name;
+
+  // for all modules list, write the detailled QGIS descriptor and add it
+  // in the algs.txt list with its group
+  while (std::getline(module_list_file, module_name)) {
+    bool descriptor_written = false;
+    Application::Pointer appli = ApplicationRegistry::
+                                  CreateApplicationFaster(module_name);
+    assert(!appli.IsNull());
+    const std::string group = appli->GetDocTags().size() > 0 ?
+                              appli->GetDocTags()[0] : "";
+    assert(!group.empty());
+
+    std::string qgis_desc_path = work_dir + module_name + ".txt";
+
+    // write detailled module desc
+    try {
+      writeModuleQGISDescriptor(appli, module_name, group, qgis_desc_path);
+      descriptor_written = true;
+    }
+    catch (const std::runtime_error& e) {
+      std::cerr << "Error while writing " << qgis_desc_path << ": " << e.what()
+                << std::endl;
+    }
 
-  const std::string group = appli->GetDocTags().size() > 0 ? appli->GetDocTags()[0] : "";
-  assert(!group.empty());
-
-  // write module
-  writeModuleQGISDescriptor(appli, module, group, output_file);
-
-  std::cerr << "Writing " << output_file << std::endl;
+    // add it in list
+    if (descriptor_written) {
+      algs_list << group << "|" << module_name << std::endl;
+    }
 
-  std::ofstream indexFile;
-  indexFile.open(algs_txt, std::ios::out | std::ios::app);
-  indexFile << group << "|" << module << std::endl;
-  indexFile.close();
-  std::cerr << "Updated " << algs_txt << std::endl;
+    appli = nullptr;
+  }
 
-  appli = nullptr;
+  algs_list.close();
+  module_list_file.close();
   ApplicationRegistry::CleanRegistry();
 
   return EXIT_SUCCESS;
-- 
GitLab