Salome HOME
updated copyright message
[tools/yacsgen.git] / module_generator / hxxparacompo.py
1 # Copyright (C) 2009-2023  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   Module that generates SALOME c++ Component from a non SALOME c++ component (its header and its shares library)
21 """
22
23 debug=1
24 import os
25 from module_generator.gener import Component, Invalid
26 from module_generator.hxx_para_tmpl import cxxService, hxxCompo, cxxCompo, cmake_src_compo_hxxpara
27 from module_generator import Service
28 from tempfile import mkstemp
29 from module_generator.yacstypes import corba_rtn_type,moduleTypes
30 from module_generator.gener import Library
31
32 class HXX2SALOMEParaComponent(Component):
33   def __init__(self, hxxfile , cpplib , cpp_path ):
34     # search a file within a directory tree
35     import fnmatch
36     def search_file(pattern, root):
37         matches = []
38         for path, dirs, files in os.walk(os.path.abspath(root)):
39             for filename in fnmatch.filter(files, pattern):
40                  matches.append(os.path.join(path, filename))
41         return matches
42
43     hxxfileful=search_file(hxxfile,cpp_path)
44     cpplibful=search_file(cpplib,cpp_path)
45     assert len(hxxfileful) > 0  ,'Error in HXX2SALOMEComponent : file ' + hxxfile + ' not found in ' + cpp_path
46     assert len(cpplibful) > 0   ,'Error in HXX2SALOMEComponent : file ' + cpplib + ' not found in ' + cpp_path
47
48     self.hxxfile=hxxfile  # to include it in servant implementation
49
50     # grab name of c++ component
51     from .hxx_awk import parse01,parse1,parse2,parse3
52     cmd1="""awk '$1 == "class" && $0 !~ /;/ {print $2}' """ + hxxfileful[0] + """|awk -F: '{printf "%s",$1}' """
53     f=os.popen(cmd1)
54     class_name=f.readlines()[0]
55     name=class_name
56     print("classname=",class_name)
57
58     if cpplib[:3]=="lib" and cpplib[-3:]==".so":
59         cpplibname=cpplib[3:-3]  # get rid of lib and .so, to use within makefile.am
60     else:
61         cpplibname=class_name+"CXX"  # the default name
62
63     f.close()
64
65     # create temporary awk files
66     (fd01,p01n)=mkstemp()
67     f01=os.fdopen(fd01,"w")
68     f01.write(parse01)
69     f01.close()
70
71     (fd1,p1n)=mkstemp()
72     f1=os.fdopen(fd1,"w")
73     f1.write(parse1)
74     f1.close()
75
76     (fd2,p2n)=mkstemp()
77     f2=os.fdopen(fd2,"w")
78     f2.write(parse2)
79     f2.close()
80
81     (fd3,p3n)=mkstemp()
82     f3=os.fdopen(fd3,"w")
83     f3.write(parse3)
84     f3.close()
85
86     # awk parsing of hxx files - result written in file parse_type_result
87     cmd2="cat " + hxxfileful[0] + " | awk -f " + p01n + """ | sed 's/virtual //g' | sed 's/MEDMEM_EXPORT//g' | sed 's/throw.*;/;/g' | awk -f """ + p1n + " | awk -f " + p2n + " | awk -v class_name=" + class_name + " -f " + p3n
88     os.system(cmd2)
89     os.remove(p01n)
90     os.remove(p1n)
91     os.remove(p2n)
92     os.remove(p3n)
93
94     # Retrieve the information which was generated in the file parse_type_result.
95     # The structure of the file is :
96     #
97     #   Function  return_type   function_name
98     #   [arg1_type  arg1_name]
99     #   [arg2_type  arg2_name]
100     #   ...
101     # The service names are stored in list_of_services
102     # The information relative to a service (called service_name) is stored in the dictionnary service_definition[service_name]
103     from .hxx_awk import cpp2idl_mapping
104     from .hxx_awk import cpp2yacs_mapping
105     cpp2yacs_mapping["const MEDCoupling::MEDCouplingFieldDouble*"]="SALOME_MED/MPIMEDCouplingFieldDoubleCorbaInterface"
106     cpp2yacs_mapping["const MEDCoupling::MEDCouplingFieldDouble&"]="SALOME_MED/MPIMEDCouplingFieldDoubleCorbaInterface"
107     cpp2yacs_mapping["MEDCoupling::MEDCouplingFieldDouble*&"]="SALOME_MED/MPIMEDCouplingFieldDoubleCorbaInterface"
108     cpp2yacs_mapping["MEDCoupling::MEDCouplingFieldDouble*"]="SALOME_MED/MPIMEDCouplingFieldDoubleCorbaInterface"
109     list_of_services=[]
110     service_definition={}
111     result_parsing=open("parse_type_result","r")
112     for line in result_parsing.readlines():
113         line=line[0:-1] # get rid of trailing \n
114         words = line.split(';')
115
116         if len(words) >=3 and words[0] == "Function": # detect a new service
117             function_name=words[2]
118             if function_name != "getInputFieldTemplate":
119                 list_of_services.append(function_name)
120             service_definition[function_name]={}
121             service_definition[function_name]["ret"]=words[1]  # return type
122             service_definition[function_name]["inports"]=[]
123             service_definition[function_name]["outports"]=[]
124             service_definition[function_name]["ports"]=[]
125             service_definition[function_name]["impl"]=[]
126             service_definition[function_name]["thread_func_decl"]=[]
127             service_definition[function_name]["thread_str_decl"]=[]
128
129         if len(words) == 2 and function_name != "getInputFieldTemplate":  # an argument type and argument name of a previous service
130             typename=words[0]
131             argname=words[1]
132             service_definition[list_of_services[-1]]["ports"].append( (argname,typename) ) # store in c++ order the arg names
133
134             # separate in from out parameters
135             inout=cpp2idl_mapping[typename][0:2]
136             assert inout=="in" or inout=="ou",'Error in table cpp2idl_mapping'
137             if inout == "in":
138                 service_definition[list_of_services[-1]]["inports"].append( (argname,typename) )
139             else:
140                 service_definition[list_of_services[-1]]["outports"].append( (argname,typename) )
141
142     if 'getInputFieldTemplate' in service_definition:
143         del service_definition['getInputFieldTemplate']
144     #
145     # generate implementation of c++ servant
146     # store it in service_definition[serv]["impl"]
147     #
148     from .hxx_awk import cpp_impl_a,cpp_impl_b,cpp_impl_c  # these tables contain the part of code which depends upon c++ types
149     cpp_impl_b["MEDCoupling::MEDCouplingFieldDouble*"]="""\tMEDCoupling::MPIMEDCouplingFieldDoubleServant * _rtn_field_i = new MEDCoupling::MPIMEDCouplingFieldDoubleServant(_orb,_poa,this,_rtn_cpp);
150 \t_rtn_cpp->decrRef();
151 \tSALOME_MED::MPIMEDCouplingFieldDoubleCorbaInterface_ptr _rtn_ior = _rtn_field_i->_this();\n"""
152     cpp_impl_a["const MEDCoupling::MEDCouplingFieldDouble*"]="\tMEDCoupling::MEDCouplingFieldDouble* _%(arg)s=cppCompo_->getInputFieldTemplate();\n\t_setInputField(%(arg)s,_%(arg)s);\n\t_initializeCoupling(%(arg)s);\n"
153
154     from .yacstypes import corbaTypes,corbaOutTypes
155     format_thread_signature="void * th_%s(void * st);" # this thread declaration will be included in servant's header
156     format_thread_struct="typedef struct {\n  int ip;\n  Engines::IORTab* tior;\n%(arg_decl)s} thread_%(serv_name)s_str;" # this thread declaration will be included in servant's header
157     format_thread_create="""
158 //      create threads to forward to other processes the service invocation
159         if(_numproc == 0)
160         {
161             th = new pthread_t[_nbproc];
162             for(int ip=1;ip<_nbproc;ip++)
163             {
164                 %(init_thread_str)s
165                 pthread_create(&(th[ip]),NULL,th_%(serv_name)s,(void*)st);
166             }
167         }
168 """
169     s_thread_join="""
170 //      waiting for all threads to complete
171         if(_numproc == 0)
172         {
173             for(int ip=1;ip<_nbproc;ip++)
174             {
175                 pthread_join(th[ip],&ret_th);
176                 est = (except_st*)ret_th;
177                 if(est->exception)
178                 {
179                     ostringstream msg;
180                     msg << "[" << ip << "] " << est->msg;
181                     THROW_SALOME_CORBA_EXCEPTION(msg.str().c_str(),SALOME::INTERNAL_ERROR);
182                 }
183                 delete est;
184             }
185           delete[] th;
186         }
187 """
188     format_thread_impl="""
189 void *th_%(serv_name)s(void *s)
190 {
191   ostringstream msg;
192   thread_%(serv_name)s_str *st = (thread_%(serv_name)s_str*)s;
193   except_st *est = new except_st;
194   est->exception = false;
195
196   try
197     {
198       %(module)s_ORB::%(component_name)s_Gen_var compo = %(module)s_ORB::%(component_name)s_Gen::_narrow((*(st->tior))[st->ip]);
199       compo->%(serv_name)s(%(arg_thread_invocation)s);
200     }
201   catch(const SALOME::SALOME_Exception &ex)
202     {
203       est->exception = true;
204       est->msg = ex.details.text;
205     }
206   catch(const CORBA::Exception &ex)
207     {
208       est->exception = true;
209       msg << "CORBA::Exception: " << ex;
210       est->msg = msg.str();
211     }
212   delete st;
213   return((void*)est);
214 }
215
216 """
217
218     self.thread_impl=""  # the implementation of the thread functions used to invoque services on slave processors 
219     for serv in list_of_services:
220         if debug:
221             print("service : ",serv)
222             print("  inports  -> ",service_definition[serv]["inports"])
223             print("  outports -> ",service_definition[serv]["outports"])
224             print("  return   -> ",service_definition[serv]["ret"])
225
226         # Part 0 : specific treatments for parallel components (call threads to forward the invocation to the service to all processes)
227         service_definition[serv]["thread_func_decl"]=format_thread_signature % serv
228         arg_declaration=""
229         arg_thread_invocation=""
230         init_thread_str="thread_%s_str *st = new thread_%s_str;" % (serv,serv) 
231         init_thread_str+="\n                st->ip = ip;"
232         init_thread_str+="\n                st->tior = _tior;"
233         for (argname,argtype) in service_definition[serv]["inports"]:
234             arg_declaration+="  "+corbaTypes[cpp2yacs_mapping[argtype]]+" "+argname+";\n"
235             init_thread_str+="\n                st->"+argname+" = "+argname+";"
236         for (argname,argtype) in service_definition[serv]["outports"]:
237             arg_declaration+="  "+corbaOutTypes[cpp2yacs_mapping[argtype]]+" "+argname+";\n"
238             init_thread_str+="\n                st->"+argname+" = "+argname+";"
239         for (argname,argtype) in service_definition[serv]["ports"]:
240             arg_thread_invocation+="st->"+argname+", "
241         if len(arg_thread_invocation)>0:
242             arg_thread_invocation=arg_thread_invocation[0:-2] # get rid of trailing comma
243         service_definition[serv]["thread_str_decl"]=format_thread_struct % { "serv_name" : serv, "arg_decl" : arg_declaration }
244         s_thread_call=format_thread_create % { "serv_name" : serv , "init_thread_str" : init_thread_str}
245         # within format_thread_impl the treatment of %(module) is postponed in makecxx() because we don't know here the module name
246         self.thread_impl+=format_thread_impl % {"serv_name" : serv , "arg_thread_invocation" : arg_thread_invocation , "component_name" : name, "module" : "%(module)s" } 
247
248         # Part 1 : Argument pre-processing
249         s_argument_processing="//\tArguments processing\n"
250         for (argname,argtype) in service_definition[serv]["inports"] + service_definition[serv]["outports"]:
251             format=cpp_impl_a[argtype]
252             s_argument_processing += format % {"arg" : argname }
253         if s_argument_processing=="//\tArguments processing\n": # if there was no args
254             s_argument_processing=""
255
256         # if an argument called name is of type const char*, this argument is transmitted to getInputFieldTemplate()
257         # => we insert "name" between the bracket of getInputFieldTemplate()
258         indice_getInputFieldTemplate=s_argument_processing.find ("cppCompo_->getInputFieldTemplate();")
259         if s_argument_processing.find ("const std::string _name") != -1  and  indice_getInputFieldTemplate != -1:
260             ind_insertion=indice_getInputFieldTemplate+33
261             s_argument_processing=s_argument_processing[:ind_insertion]+"name"+s_argument_processing[ind_insertion:-1]
262
263         # Part 2 : Call to the underlying c++ function
264         s_call_cpp_function="//\tCall cpp component\n\t"
265         rtn_type=service_definition[serv]["ret"]
266         if rtn_type == "void" : # if return type is void, the call syntax is different
267             s_call_cpp_function += "cppCompo_->%s(" % serv
268         else:
269             s_call_cpp_function += "%s _rtn_cpp = cppCompo_->%s(" % (rtn_type ,serv )
270
271         for (argname,argtype) in service_definition[serv]["ports"]:
272               # special treatment for some arguments
273               post=""
274               pre=""
275               if cpp_impl_a[argtype].find("auto_ptr" ) != -1 :
276                   post=".get()" # for auto_ptr argument, retrieve the raw pointer behind
277               if  argtype == "const MEDMEM::MESH&"  or  argtype == "const MEDMEM::SUPPORT&" : 
278                   pre="*"  # we cannot create MESHClient on the stack (private constructor), so we create it on the heap and dereference it
279               post+="," # separator between arguments
280               s_call_cpp_function += " %s_%s%s" % ( pre,argname,post)
281         if s_call_cpp_function[-1]==',':
282             s_call_cpp_function=s_call_cpp_function[0:-1] # get rid of trailing comma
283         s_call_cpp_function=s_call_cpp_function+');\n'
284
285         # Part 3.a : Out Argument Post-processing
286         s_argument_postprocessing="//\tPost-processing & return\n"
287         for (argname,argtype) in service_definition[serv]["outports"]:
288             format=cpp_impl_c[argtype]
289             s_argument_postprocessing += format % {"arg" : argname, "module" : "%(module)s" } # the treatment of %(module) is postponed in makecxx() 
290                                                                                               # because we don't know here the module name
291         # Part 3.b : In Argument Post-processing
292         for (argname,argtype) in service_definition[serv]["inports"]:
293             if argtype in cpp_impl_c: # not all in types require a treatment
294                 format=cpp_impl_c[argtype]
295                 s_argument_postprocessing += format % {"arg" : argname, "module" : "%(module)s" } # id : treatment of %(module) is postponed in makecxx
296
297         # Part 3.c : return processing
298         s_rtn_processing=cpp_impl_b[rtn_type]
299         if  rtn_type != "void":
300             s_rtn_processing += "\treturn _rtn_ior;"
301
302         service_definition[serv]["impl"] = s_thread_call + s_argument_processing + s_call_cpp_function + s_thread_join  + s_argument_postprocessing + s_rtn_processing
303         if debug:
304             print("implementation :\n",service_definition[serv]["impl"])
305
306     #
307     # Create a list of Service objects (called services), and give it to Component constructor
308     #
309     services=[]
310     self.use_medmem=False
311     self.use_medcoupling=False
312     self.thread_func_decl=[]
313     self.thread_str_decl=[]
314     for serv in list_of_services:
315         # for inports and outports, Service class expects a list of tuples, each tuple containing the name and the yacs type of the port
316         # thus we need to convert c++ types to yacs types  (we use for that the cpp2yacs_mapping table
317         inports=[]
318         for i in range( len(service_definition[serv]["inports"]) ):
319             inports.append( [service_definition[serv]["inports"][i][0], cpp2yacs_mapping[service_definition[serv]["inports"][i][1]] ] )
320         outports=[]
321         for i in range( len(service_definition[serv]["outports"]) ):
322             outports.append( [service_definition[serv]["outports"][i][0], cpp2yacs_mapping[service_definition[serv]["outports"][i][1]] ] )
323
324         Return="void"
325         if service_definition[serv]["ret"] != "void":
326             Return=cpp2yacs_mapping[service_definition[serv]["ret"]]
327
328         # find out if component uses medmem types and/or medcoupling types
329         for (argname,argtype) in inports + outports + [("return",Return)]:
330             if moduleTypes[argtype]=="MED":
331                 if argtype.count("Coupling")>0:
332                     self.use_medcoupling=True
333                 else:
334                     self.use_medmem=True
335                 break
336
337         code=service_definition[serv]["impl"]
338         if debug:
339             print("service : ",serv)
340             print("  inports  -> ",service_definition[serv]["inports"])
341             print("  converted inports  -> ",inports)
342             print("  outports -> ",service_definition[serv]["outports"])
343             print("  converted outports  -> ",outports)
344             print("  Return  -> ",service_definition[serv]["ret"])
345             print("  converted Return  -> ",Return)
346
347         services.append(Service(serv, 
348            inport=inports, 
349            outport=outports,
350            ret=Return, 
351            defs="", 
352            body=code,
353            ) )
354         self.thread_func_decl.append(service_definition[serv]["thread_func_decl"])
355         self.thread_str_decl.append(service_definition[serv]["thread_str_decl"])
356 #    Includes="-I${"+name+"CPP_ROOT_DIR}/include"
357     Includes = os.path.join(cpp_path, "include")
358 #    Libs="-L${"+name+"CPP_ROOT_DIR}/lib -l"+cpplibname
359 #    Libs=[cpplibname+" PATH "+ os.path.join(cpp_path, "lib") ]
360     Libs = [ Library( name=cpplibname, path=os.path.join(cpp_path, "lib"))]
361     Compodefs=""
362     Inheritedclass=""
363     self.inheritedconstructor=""
364     Compodefs="""
365 #include CORBA_SERVER_HEADER(MEDCouplingCorbaServantTest)
366 #include "MPIMEDCouplingFieldDoubleServant.hxx"
367 """
368
369     Component.__init__(self, name, services, impl="CPP", libs=Libs,
370                              rlibs="", includes=Includes, kind="lib",
371                              sources=None,inheritedclass=Inheritedclass,
372                              compodefs=Compodefs)
373
374 # -----------------------------------------------------------------------------      
375   def libraryName(self):
376     """ Name of the target library
377     """
378     return self.name + "Engine"
379     
380 # ------------------------------------------------------------------------------
381   def targetProperties(self):
382     """ define the rpath property of the target using self.rlibs
383     return
384       string containing the commands to add to cmake
385     """
386     text=""
387     if self.rlibs.strip() :
388       text="SET_TARGET_PROPERTIES( %sEngine PROPERTIES INSTALL_RPATH %s)\n" % (self.name, self.rlibs)
389     return text
390
391 # ------------------------------------------------------------------------------
392   def makeCompo(self, gen):
393     """generate files for C++ component
394
395        return a dict where key is the file name and value is the content of the file
396     """
397     cxxfile = "%s_i.cxx" % self.name
398     hxxfile = "%s_i.hxx" % self.name
399     (cmake_text, cmake_vars) = self.additionalLibraries()
400     
401     cmakelist_content = cmake_src_compo_hxxpara.substitute(
402                         module = gen.module.name,
403                         component = self.name,
404                         componentlib = self.libraryName(),
405                         includes = self.includes,
406                         libs = cmake_vars,
407                         find_libs = cmake_text,
408                         target_properties = self.targetProperties()
409                         )
410     
411     return {"CMakeLists.txt":cmakelist_content,
412             cxxfile:self.makecxx(gen),
413             hxxfile:self.makehxx(gen)
414            }
415
416 #  def getMakefileItems(self,gen):
417 #      makefileItems={"header":"""
418 #include $(top_srcdir)/adm_local/make_common_starter.am
419 #
420 #"""}
421 #      makefileItems["lib_LTLIBRARIES"]=["lib"+self.name+"Engine.la"]
422 #      makefileItems["salomeinclude_HEADERS"]=["%s_i.hxx" % self.name]
423 #      makefileItems["body"]=compoMakefile.substitute(module=gen.module.name,
424 #                                                     component=self.name,
425 #                                                     libs=self.libs,
426 #                                                     includes=self.includes)
427 #      return makefileItems
428
429   def makehxx(self, gen):
430     """return a string that is the content of .hxx file
431     """
432     services = []
433     for serv in self.services:
434       service = "    %s %s(" % (corba_rtn_type(serv.ret,gen.module.name),serv.name)
435       service = service+gen.makeArgs(serv)+") throw (SALOME::SALOME_Exception);"
436       services.append(service)
437     servicesdef = "\n".join(services)
438
439     inheritedclass=self.inheritedclass
440     thread_func_decl="\n".join(self.thread_func_decl)
441     thread_str_decl="\n".join(self.thread_str_decl)
442     if debug:
443         print("thread_func_decl : ")
444         print(thread_func_decl)
445         print("thread_str_decl : ")
446         print(thread_str_decl)
447
448     if self.inheritedclass:
449       inheritedclass= " public virtual " + self.inheritedclass + ","
450
451     return hxxCompo.substitute(component=self.name, module=gen.module.name, thread_func_decl=thread_func_decl,
452                                thread_str_decl=thread_str_decl, servicesdef=servicesdef, inheritedclass=inheritedclass,
453                                compodefs=self.compodefs)
454
455   def makecxx(self, gen, exe=0):
456     """return a string that is the content of .cxx file
457     """
458     services = []
459     inits = []
460     defs = []
461     for serv in self.services:
462       defs.append(serv.defs)
463       service = cxxService.substitute(component=self.name, service=serv.name,ret=corba_rtn_type(serv.ret,gen.module.name),
464                                       parameters=gen.makeArgs(serv),
465                                       body=serv.body % {"module":gen.module.name+"_ORB"} )
466       services.append(service)
467     return cxxCompo.substitute(component=self.name, cxx_include_file=self.hxxfile,
468                                inheritedconstructor=self.inheritedconstructor,
469                                servicesdef="\n".join(defs),
470                                servicesimpl="\n".join(services),
471                                thread_impl=self.thread_impl % {"module":gen.module.name} )
472
473   def getIdlInterfaces(self):
474     services = self.getIdlServices()
475     from .hxx_tmpl import interfaceidlhxx
476     Inherited=""
477     Inherited="SALOME_MED::ParaMEDMEMComponent"
478     return interfaceidlhxx.substitute(component=self.name,inherited=Inherited, services="\n".join(services))
479
480   def getIdlDefs(self):
481     idldefs="""#include "ParaMEDMEMComponent.idl"\n"""
482     if self.interfacedefs:
483       idldefs = idldefs + self.interfacedefs
484     return idldefs
485
486   def getDependentModules(self):
487     """ This component depends on "MED" because it inherits from ParaMEDMEMComponent
488     """
489     depend_modules = Component.getDependentModules(self)
490     depend_modules.add("MED")
491     return depend_modules