Salome HOME
Fixed regression when creating executable components.
[tools/yacsgen.git] / module_generator / pycompo.py
1 # Copyright (C) 2009-2014  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 """
21   Module that defines PYComponent for SALOME components implemented in Python
22 """
23 import os
24 from gener import Component, Invalid
25 from pyth_tmpl import pyinitService, pyService, pyCompoEXE, pyCompo, cmake_src_compo_py
26 import textwrap
27 from string import split,rstrip,join
28
29 def indent(text, prefix='    '):
30   """Indent text by prepending a given prefix to each line."""
31   if not text: return ''
32   lines = split(text, '\n')
33   lines = map(lambda line, prefix=prefix: prefix + line, lines)
34   if lines: lines[-1] = rstrip(lines[-1])
35   return join(lines, '\n')
36
37 class PYComponent(Component):
38   """
39    A :class:`PYComponent` instance represents a Python SALOME component with services given as a list of :class:`Service`
40    instances with the parameter *services*.
41
42    :param name: gives the name of the component.
43    :type name: str
44    :param services: the list of services (:class:`Service`) of the component.
45    :param kind: If it is given and has the value "exe", the component will be built as a standalone
46       component (python executable). The default is to build the component as a python module.
47    :param sources: gives all the external Python source files to add in the component directory (list of paths).
48    :param python_path: If it is given (as a list of paths), all the paths are added to the python path (sys.path).
49    :param compodefs: can be used to add extra definition code in the component for example when using a base class
50       to define the component class by deriving it (see *inheritedclass* parameter)
51    :param inheritedclass: can be used to define a base class for the component. The base class can be defined in external
52       source or with the *compodefs* parameter. The value of the *inheritedclass* parameter is the name of the base class.
53    :param idls: can be used to add extra idl CORBA interfaces. This parameter must gives a list of idl file names that are
54       added into the generated module (idl directory) and compiled with the generated idl of the module.
55    :param interfacedefs: can be used to add idl definitions (or includes of idl files) into the generated idl of the module.
56    :param inheritedinterface: can be used to make the component inherit an extra idl interface that has been included through
57       the *idls* and *interfacedefs* parameters. See the pygui1 example for how to use these last parameters.
58
59    For example, the following call defines a Python component named "mycompo" with one service s1 (it must have been defined before)::
60
61       >>> c1 = module_generator.PYComponent('mycompo', services=[s1,],
62                                                        python_path="apath")
63
64   """
65   def __init__(self, name, services=None, kind="lib", sources=None, python_path=None,
66                      compodefs="", inheritedclass="", idls=None, interfacedefs="", inheritedinterface=""):
67     """initialise component attributes"""
68     self.python_path = python_path or []
69     Component.__init__(self, name, services, impl="PY", kind=kind, sources=sources,
70                              inheritedclass=inheritedclass, compodefs=compodefs,
71                              idls=idls,interfacedefs=interfacedefs,inheritedinterface=inheritedinterface)
72
73   def validate(self):
74     """validate component attributes"""
75     Component.validate(self)
76     kinds = ("lib","exe")
77     if self.kind not in kinds:
78       raise Invalid("kind must be one of %s for component %s" % (kinds,self.name))
79
80   def libraryName(self):
81     """ Name of the target library
82         No library for a python component
83     """
84     return ""
85     
86   def makeCompo(self, gen):
87     """generate component sources as a dictionary containing
88        file names (key) and file content (values)
89     """
90     pyfile = ""
91     file_content = ""
92     if self.kind == "lib":
93       pyfile = self.name  + ".py"
94       file_content = self.makepy(gen)
95     elif self.kind == "exe":
96       pyfile = self.name + ".exe"
97       file_content = self.makepyexe(gen)
98     else :
99       raise Invalid("Invalid kind ()%s for component %s" % (
100                                     self.kind, self.name))
101     
102     sources = pyfile + "".join(map(lambda x: "\n  " + os.path.basename(x),
103                                    self.sources))
104     cmake_content = cmake_src_compo_py.substitute(sources=sources)
105     
106     return {"CMakeLists.txt":cmake_content,
107             pyfile:file_content
108            }
109
110   def makepy(self, gen):
111     """generate standard SALOME component source (python module)"""
112     services = []
113     inits = []
114     defs = []
115     for serv in self.services:
116       defs.append(serv.defs)
117       params = []
118       pyparams = []
119       for name, typ in serv.inport:
120         if typ=="file":continue #files are not passed through service interface
121         params.append(name)
122         if typ == "pyobj":
123           pyparams.append("      %s=cPickle.loads(%s)" %(name, name))
124       inparams = ",".join(params)
125       convertinparams = '\n'.join(pyparams)
126
127       params = []
128       pyparams = []
129       for name, typ in serv.outport:
130         if typ=="file":continue #files are not passed through service interface
131         params.append(name)
132         if typ == "pyobj":
133           pyparams.append("      %s=cPickle.dumps(%s,-1)" %(name, name))
134       outparams = ",".join(params)
135       convertoutparams = '\n'.join(pyparams)
136       #dedent and indent the body
137       body=textwrap.dedent(serv.body)
138       body=indent(body,' '*6)
139
140       service = pyService.substitute(component=self.name, service=serv.name, inparams=inparams,
141                                      outparams=outparams, body= body,
142                                      convertinparams=convertinparams,
143                                      convertoutparams=convertoutparams)
144       streams = []
145       for name, typ, dep in serv.instream:
146         streams.append('       calcium.create_calcium_port(self.proxy,"%s","%s","IN","%s")'% (name, typ, dep))
147       instream = "\n".join(streams)
148       streams = []
149       for name, typ, dep in serv.outstream:
150         streams.append('       calcium.create_calcium_port(self.proxy,"%s","%s","OUT","%s")'% (name, typ, dep))
151       outstream = "\n".join(streams)
152
153       init = pyinitService.substitute(component=self.name, service=serv.name,
154                                       instream=instream, outstream=outstream)
155       services.append(service)
156       inits.append(init)
157
158     python_path = ",".join([repr(p) for p in self.python_path])
159
160     inheritedclass=self.inheritedclass
161     callconstructor=""
162     if self.inheritedclass:
163       inheritedclass= self.inheritedclass + ","
164       callconstructor="""
165     if hasattr(%s,"__init__"):
166       %s.__init__(self)""" % (self.inheritedclass,self.inheritedclass)
167
168     return pyCompo.substitute(component=self.name, module=gen.module.name,
169                               servicesdef="\n".join(defs), servicesimpl="\n".join(services),
170                               initservice='\n'.join(inits),
171                               python_path=python_path,inheritedclass=inheritedclass,
172                               compodefs=self.compodefs, callconstructor=callconstructor)
173
174   def makepyexe(self, gen):
175     """generate standalone component source (python executable)"""
176     services = []
177     inits = []
178     defs = []
179     for serv in self.services:
180       defs.append(serv.defs)
181       params = []
182       pyparams = []
183       for name, typ in serv.inport:
184         if typ=="file":continue #files are not passed through service interface
185         params.append(name)
186         if typ == "pyobj":
187           pyparams.append("      %s=cPickle.loads(%s)" %(name, name))
188       inparams = ",".join(params)
189       convertinparams = '\n'.join(pyparams)
190
191       params = []
192       pyparams = []
193       for name, typ in serv.outport:
194         if typ=="file":continue #files are not passed through service interface
195         params.append(name)
196         if typ == "pyobj":
197           pyparams.append("      %s=cPickle.dumps(%s,-1)" %(name, name))
198       outparams = ",".join(params)
199       convertoutparams = '\n'.join(pyparams)
200       #dedent and indent the body
201       body=textwrap.dedent(serv.body)
202       body=indent(body,' '*6)
203       service = pyService.substitute(component=self.name, service=serv.name,
204                                      inparams=inparams, outparams=outparams,
205                                      body=body,
206                                      convertinparams=convertinparams,
207                                      convertoutparams=convertoutparams,
208                                     )
209       streams = []
210       for name, typ, dep in serv.instream:
211         streams.append('       calcium.create_calcium_port(self.proxy,"%s","%s","IN","%s")'% (name, typ, dep))
212       instream = "\n".join(streams)
213       streams = []
214       for name, typ, dep in serv.outstream:
215         streams.append('       calcium.create_calcium_port(self.proxy,"%s","%s","OUT","%s")'% (name, typ, dep))
216       outstream = "\n".join(streams)
217
218       init = pyinitService.substitute(component=self.name, service=serv.name,
219                                       instream=instream, outstream=outstream)
220       services.append(service)
221       inits.append(init)
222
223     python_path = ",".join([repr(p) for p in self.python_path])
224
225     inheritedclass=self.inheritedclass
226     callconstructor=""
227     if self.inheritedclass:
228       inheritedclass= self.inheritedclass + ","
229       callconstructor="""
230     if hasattr(%s,"__init__"):
231       %s.__init__(self)""" % (self.inheritedclass,self.inheritedclass)
232
233     return pyCompoEXE.substitute(component=self.name, module=gen.module.name,
234                                  servicesdef="\n".join(defs),
235                                  servicesimpl="\n".join(services),
236                                  initservice='\n'.join(inits),
237                                  python_path=python_path,inheritedclass=inheritedclass,
238                                  compodefs=self.compodefs, callconstructor=callconstructor)
239