Salome HOME
ef7073dea57a842020a8db8bebfade07a91f4f10
[modules/shaper.git] / src / Config / Config_ModuleReader.cpp
1 // Copyright (C) 2014-2023  CEA, EDF
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Lesser General Public License for more details.
12 //
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 //
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 //
19
20 #include <pyconfig.h>
21
22 #include <Config_Keywords.h>
23 #include <Config_Common.h>
24 #include <Config_ModuleReader.h>
25 #include <Config_FeatureReader.h>
26 #include <Config_PluginMessage.h>
27 #include <Events_InfoMessage.h>
28
29 #include <libxml/parser.h>
30 #include <libxml/tree.h>
31
32 // Have to be included before std headers
33 #include <Python.h>
34 #include <structmember.h>
35
36 //Necessary for cerr
37 #include <iostream>
38 #include <algorithm>
39
40 #ifdef WIN32
41 #include <windows.h>
42 #pragma warning(disable : 4996) // for getenv
43 #else
44 #include <dlfcn.h>
45 #endif
46
47 std::map<std::string, Config_ModuleReader::PluginType> Config_ModuleReader::myPluginTypes;
48 std::set<std::string> Config_ModuleReader::myDependencyModules;
49
50 Config_ModuleReader::Config_ModuleReader(const char* theEventGenerated)
51     : Config_XMLReader(PLUGIN_FILE),
52       myEventGenerated(theEventGenerated)
53 {
54 }
55
56 Config_ModuleReader::~Config_ModuleReader()
57 {
58 }
59
60 const std::map<std::string, std::string>& Config_ModuleReader::featuresInFiles() const
61 {
62   return myFeaturesInFiles;
63 }
64
65 const std::map<std::string, std::string>& Config_ModuleReader::proprietaryFeatures() const
66 {
67   return myProprietaryFeatures;
68 }
69
70 const std::set<std::string>& Config_ModuleReader::proprietaryPlugins() const
71 {
72   return myProprietaryPlugins;
73 }
74
75 const std::set<std::string>& Config_ModuleReader::modulePluginFiles() const
76 {
77   return myPluginFiles;
78 }
79
80 /*!
81  * Get module name from plugins.xml
82  * (property "module")
83  */
84 std::string Config_ModuleReader::getModuleName()
85 {
86   xmlNodePtr aRoot = findRoot();
87   return getProperty(aRoot, PLUGINS_MODULE);
88 }
89
90
91 void Config_ModuleReader::addFeature(const std::string& theFeatureName,
92                                      const std::string& thePluginConfig)
93 {
94   if (myProprietaryFeatures.count(theFeatureName)) {
95     myProprietaryFeatures.erase(theFeatureName);
96   }
97
98   if (myFeaturesInFiles.count(theFeatureName)) {
99     std::string anErrorMsg = "Can not register feature '%1' in plugin '%2'."
100       " There is a feature with the same ID.";
101     Events_InfoMessage("Config_ModuleReader", anErrorMsg)
102       .arg(theFeatureName).arg(thePluginConfig).send();
103     return;
104   }
105
106   myFeaturesInFiles[theFeatureName] = thePluginConfig;
107 }
108
109 void Config_ModuleReader::addFeatureRequireLicense(const std::string& theFeatureName,
110                                                    const std::string& thePluginConfig)
111 {
112   if (myFeaturesInFiles.count(theFeatureName) ||
113       myProprietaryFeatures.count(theFeatureName)) {
114     std::string anErrorMsg = "Can not register feature '%1' in plugin '%2'."
115       " There is a feature with the same ID.";
116     Events_InfoMessage("Config_ModuleReader", anErrorMsg)
117       .arg(theFeatureName).arg(thePluginConfig).send();
118     return;
119   }
120
121   myProprietaryFeatures[theFeatureName] = thePluginConfig;
122 }
123
124 void Config_ModuleReader::processNode(xmlNodePtr theNode)
125 {
126   if (isNode(theNode, NODE_PLUGIN, NULL)) {
127     if (!hasRequiredModules(theNode))
128       return;
129     std::string aPluginConf = getProperty(theNode, PLUGIN_CONFIG);
130     if (!aPluginConf.empty())
131       myPluginFiles.insert(aPluginConf);
132     std::string aPluginLibrary = getProperty(theNode, PLUGIN_LIBRARY);
133     std::string aPluginScript = getProperty(theNode, PLUGIN_SCRIPT);
134     std::string aPluginName = addPlugin(aPluginLibrary, aPluginScript, aPluginConf);
135     std::string aPluginDocSection = getProperty(theNode, PLUGIN_DOCSECTION);
136     std::string aUsesPlugin = getProperty(theNode, PLUGIN_USES);
137     if (!aUsesPlugin.empty()) { // send information about the plugin dependencies
138       std::shared_ptr<Config_PluginMessage> aMess(new Config_PluginMessage(
139         Events_Loop::loop()->eventByName(Config_PluginMessage::EVENT_ID()), aPluginName));
140       aMess->setUses(aUsesPlugin);
141       Events_Loop::loop()->send(aMess);
142     }
143
144     std::string aLicense = getProperty(theNode, PLUGIN_LICENSE);
145     std::transform(aLicense.begin(), aLicense.end(), aLicense.begin(), ::tolower);
146     bool isLicensed = aLicense == "true";
147     if (isLicensed)
148       myProprietaryPlugins.insert(aPluginName);
149
150     std::list<std::string> aFeatures = importPlugin(aPluginName, aPluginConf, aPluginDocSection);
151     std::list<std::string>::iterator it = aFeatures.begin();
152     for (; it != aFeatures.end(); it++) {
153       if (isLicensed)
154         addFeatureRequireLicense(*it, aPluginConf);
155       else
156         addFeature(*it, aPluginConf);
157     }
158   }
159 }
160
161 bool Config_ModuleReader::processChildren(xmlNodePtr theNode)
162 {
163   return isNode(theNode, NODE_PLUGINS, NULL);
164 }
165
166 bool Config_ModuleReader::hasRequiredModules(xmlNodePtr theNode) const
167 {
168   std::string aRequiredModule = normalize(getProperty(theNode, PLUGIN_DEPENDENCY));
169   if(aRequiredModule.empty())
170     return true;
171   std::set<std::string>::iterator it = myDependencyModules.begin();
172   for ( ; it != myDependencyModules.end(); it++ ) {
173     if (*it == aRequiredModule) return true;
174   }
175   return false;
176 }
177
178 std::list<std::string> Config_ModuleReader::importPlugin(const std::string& thePluginLibrary,
179                                                          const std::string& thePluginXmlConf,
180                                                          const std::string& thePluginDocSection)
181 {
182   if (thePluginXmlConf.empty()) {  //probably a third party library
183     loadLibrary(thePluginLibrary);
184     return std::list<std::string>();
185   }
186
187   Config_FeatureReader aReader = Config_FeatureReader(thePluginXmlConf,
188                                                       thePluginLibrary,
189                                                       thePluginDocSection,
190                                                       myEventGenerated);
191   aReader.readAll();
192   return aReader.features();
193 }
194
195 std::string Config_ModuleReader::addPlugin(const std::string& aPluginLibrary,
196                                            const std::string& aPluginScript,
197                                            const std::string& aPluginConf)
198 {
199   PluginType aType = Config_ModuleReader::Binary;
200   std::string aPluginName;
201   if (!aPluginLibrary.empty()) {
202     aPluginName = aPluginLibrary;
203     if (aPluginConf.empty()) {
204       aType = Config_ModuleReader::Intrenal;
205     }
206   } else if (!aPluginScript.empty()) {
207     aPluginName = aPluginScript;
208     aType = Config_ModuleReader::Python;
209   }
210   if(!aPluginName.empty()) {
211     myPluginTypes[aPluginName] = aType;
212   }
213   addDependencyModule(aPluginName);
214   return aPluginName;
215 }
216
217 void Config_ModuleReader::loadPlugin(const std::string& thePluginName)
218 {
219   // informs model that plugin loading is started
220   static const Events_ID kEVENT_ID =
221     Events_Loop::loop()->eventByName(Config_PluginMessage::EVENT_ID());
222   std::shared_ptr<Config_PluginMessage> aMess(new Config_PluginMessage(kEVENT_ID, thePluginName));
223   Events_Loop::loop()->send(aMess);
224
225   PluginType aType = Config_ModuleReader::Binary;
226   if(myPluginTypes.find(thePluginName) != myPluginTypes.end()) {
227     aType = myPluginTypes.at(thePluginName);
228   }
229   switch (aType) {
230     case Config_ModuleReader::Python:
231       loadScript(thePluginName);
232       break;
233     case Config_ModuleReader::Binary:
234     case Config_ModuleReader::Intrenal:
235     default:
236       loadLibrary(thePluginName);
237       break;
238   }
239 }
240
241 namespace
242 {
243   // Handle Python exception
244   // Reuse code from KERNEL module
245   typedef struct
246   {
247     PyObject_HEAD
248     int softspace;
249     std::string *out;
250   } PyStdOut;
251
252   static void
253   PyStdOut_dealloc(PyStdOut *self)
254   {
255     PyObject_Del(self);
256   }
257
258   static PyObject*
259   PyStdOut_write(PyStdOut* self, PyObject* args)
260   {
261     char *c;
262     if (!PyArg_ParseTuple(args, "s", &c))
263       return NULL;
264
265     *(self->out) = *(self->out) + c;
266
267     Py_INCREF(Py_None);
268     return Py_None;
269   }
270
271   static PyMethodDef PyStdOut_methods[] =
272   {
273     {"write",  (PyCFunction)PyStdOut_write,  METH_VARARGS,
274       PyDoc_STR("write(string) -> None")},
275     {0, 0, 0, 0}  /* sentinel */
276   };
277
278   static PyMemberDef PyStdOut_memberlist[] =
279   {
280     {(char*)"softspace", T_INT, offsetof(PyStdOut, softspace), 0,
281      (char*)"flag indicating that a space needs to be printed; used by print"},
282     {0, 0, 0, 0, 0}   /* sentinel */
283   };
284
285   static PyTypeObject PyStdOut_Type =
286   {
287     /* The ob_type field must be initialized in the module init function
288      * to be portable to Windows without using C++. */
289     PyVarObject_HEAD_INIT(NULL, 0)
290     /* 0, */                      /*ob_size*/
291     "PyOut",                      /*tp_name*/
292     sizeof(PyStdOut),             /*tp_basicsize*/
293     0,                            /*tp_itemsize*/
294     /* methods */
295     (destructor)PyStdOut_dealloc, /*tp_dealloc*/
296     0,                            /*tp_print*/
297     0,                            /*tp_getattr*/
298     0,                            /*tp_setattr*/
299     0,                            /*tp_compare*/
300     0,                            /*tp_repr*/
301     0,                            /*tp_as_number*/
302     0,                            /*tp_as_sequence*/
303     0,                            /*tp_as_mapping*/
304     0,                            /*tp_hash*/
305     0,                            /*tp_call*/
306     0,                            /*tp_str*/
307     PyObject_GenericGetAttr,      /*tp_getattro*/
308     /* softspace is writable:  we must supply tp_setattro */
309     PyObject_GenericSetAttr,      /* tp_setattro */
310     0,                            /*tp_as_buffer*/
311     Py_TPFLAGS_DEFAULT,           /*tp_flags*/
312     0,                            /*tp_doc*/
313     0,                            /*tp_traverse*/
314     0,                            /*tp_clear*/
315     0,                            /*tp_richcompare*/
316     0,                            /*tp_weaklistoffset*/
317     0,                            /*tp_iter*/
318     0,                            /*tp_iternext*/
319     PyStdOut_methods,             /*tp_methods*/
320     PyStdOut_memberlist,          /*tp_members*/
321     0,                            /*tp_getset*/
322     0,                            /*tp_base*/
323     0,                            /*tp_dict*/
324     0,                            /*tp_descr_get*/
325     0,                            /*tp_descr_set*/
326     0,                            /*tp_dictoffset*/
327     0,                            /*tp_init*/
328     0,                            /*tp_alloc*/
329     0,                            /*tp_new*/
330     0,                            /*tp_free*/
331     0,                            /*tp_is_gc*/
332     0,                            /*tp_bases*/
333     0,                            /*tp_mro*/
334     0,                            /*tp_cache*/
335     0,                            /*tp_subclasses*/
336     0,                            /*tp_weaklist*/
337     0,                            /*tp_del*/
338     0,                            /*tp_version_tag*/
339     0,                            /*tp_finalize*/
340   };
341
342   PyObject* newPyStdOut(std::string& out)
343   {
344     PyStdOut* self = PyObject_New(PyStdOut, &PyStdOut_Type);
345     if (self) {
346       self->softspace = 0;
347       self->out=&out;
348     }
349     return (PyObject*)self;
350   }
351
352   std::string parseException()
353   {
354     std::string error;
355     if (PyErr_Occurred())
356     {
357       PyObject* new_stderr = newPyStdOut(error);
358       PyObject* old_stderr = PySys_GetObject((char*)"stderr");
359       Py_INCREF(old_stderr);
360       PySys_SetObject((char*)"stderr", new_stderr);
361       PyErr_Print();
362       PySys_SetObject((char*)"stderr", old_stderr);
363       Py_DECREF(new_stderr);
364     }
365     return error;
366   }
367 }
368
369 void Config_ModuleReader::loadScript(const std::string& theFileName, bool theSendErr)
370 {
371   /* acquire python thread */
372   PyGILState_STATE gstate = PyGILState_Ensure();
373
374   PyObject* module = PyImport_ImportModule(theFileName.c_str());
375   if (!module) {
376     std::string anErrorMsg = "An error occurred while importing " + theFileName;
377     // Get detailed error message (including traceback)
378     if (PyErr_Occurred()) {
379       std::string aPyError = parseException();
380       if (!aPyError.empty())
381         anErrorMsg += ":\n" + aPyError;
382     }
383     if (theSendErr)
384       Events_InfoMessage("Config_ModuleReader", anErrorMsg).send();
385   }
386
387   /* release python thread */
388   PyGILState_Release(gstate);
389 }
390
391 void Config_ModuleReader::loadLibrary(const std::string& theLibName)
392 {
393   std::string aFileName = library(theLibName);
394   if (aFileName.empty())
395     return;
396
397   #ifdef WIN32
398   HINSTANCE aModLib = ::LoadLibraryA(aFileName.c_str());
399   #else
400   void* aModLib = dlopen( aFileName.c_str(), RTLD_LAZY | RTLD_GLOBAL );
401   #endif
402   if(!aModLib && theLibName != "DFBrowser") { // don't show error for internal debugging tool
403 // LCOV_EXCL_START
404     std::string anErrorMsg = "Failed to load " + aFileName;
405     #ifdef WIN32
406     DWORD   dwLastError = ::GetLastError();
407     LPSTR messageBuffer = NULL;
408     size_t size = ::FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
409                                    FORMAT_MESSAGE_FROM_SYSTEM |
410                                    FORMAT_MESSAGE_IGNORE_INSERTS,
411                                    NULL,
412                                    dwLastError,
413                                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
414                                    (LPSTR)&messageBuffer, 0, NULL);
415     anErrorMsg += ": " +  std::string(messageBuffer, size);
416     #else
417     anErrorMsg += ": " + std::string(dlerror());
418     #endif
419     std::cerr << anErrorMsg << std::endl;
420     Events_InfoMessage("Config_ModuleReader", anErrorMsg).send();
421 // LCOV_EXCL_STOP
422   }
423 }
424
425 void Config_ModuleReader::addDependencyModule(const std::string& theModuleName)
426 {
427   myDependencyModules.insert(normalize(theModuleName));
428 }
429