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