Salome HOME
Merge from V6_main_20120808 08Aug12
[tools/yacsgen.git] / module_generator / gener.py
1 # Copyright (C) 2009-2012  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.
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 import os, shutil, glob, socket
21 import traceback
22 import warnings
23
24 try:
25   from string import Template
26 except:
27   from compat import Template, set
28
29 class Invalid(Exception):
30   pass
31
32 debug=0
33
34 from mod_tmpl import resMakefile, makecommon, configure, paco_configure
35 from mod_tmpl import mainMakefile, autogen, application
36 from mod_tmpl import check_sphinx
37 from cata_tmpl import catalog, interface, idl, idlMakefile, parallel_interface
38 from cata_tmpl import xml, xml_interface, xml_service
39 from cata_tmpl import idlMakefilePaCO_BUILT_SOURCES, idlMakefilePaCO_nodist_salomeinclude_HEADERS
40 from cata_tmpl import idlMakefilePACO_salomepython_DATA, idlMakefilePACO_salomeidl_DATA
41 from cata_tmpl import idlMakefilePACO_INCLUDES
42 from cata_tmpl import cataOutStream, cataInStream, cataOutparam, cataInparam
43 from cata_tmpl import cataOutParallelStream, cataInParallelStream
44 from cata_tmpl import cataService, cataCompo
45 from aster_tmpl import check_aster
46 from salomemodules import salome_modules
47 from yacstypes import corbaTypes, corbaOutTypes, moduleTypes, idlTypes, corba_in_type, corba_out_type
48 from yacstypes import ValidTypes, PyValidTypes, calciumTypes, DatastreamParallelTypes
49 from yacstypes import ValidImpl, ValidImplTypes, ValidStreamTypes, ValidParallelStreamTypes, ValidDependencies
50 from gui_tmpl import pyguimakefile, pysalomeapp, cppguimakefile, cppsalomeapp
51 from doc_tmpl import docmakefile, docconf, docsalomeapp
52
53 def makedirs(namedir):
54   """Create a new directory named namedir. If a directory already exists copy it to namedir.bak"""
55   if os.path.exists(namedir):
56     dirbak = namedir+".bak"
57     if os.path.exists(dirbak):
58       shutil.rmtree(dirbak)
59     os.rename(namedir, dirbak)
60     os.listdir(dirbak) #needed to update filesystem on special machines (cluster with NFS, for example)
61   os.makedirs(namedir)
62
63 class Module(object):
64   """
65    A :class:`Module` instance represents a SALOME module that contains components given as a list of
66    component instances (:class:`CPPComponent` or :class:`PYComponent` or :class:`F77Component` or :class:`ASTERComponent`)
67    with the parameter *components*.
68
69    :param name: gives the name of the module. The SALOME source module
70       will be located in the <name_SRC> directory.
71    :type name: str
72    :param components: gives the list of components of the module.
73    :param prefix: is the path of the installation directory.
74    :param layout: If given and has the value "monodir", all components
75       will be generated in a single directory. The default is to generate each component in its
76       own directory.
77    :param doc: can be used to add an online documentation to the module. It must be a list of file names (sources, images, ...) that will be
78       used to build a sphinx documentation (see http://sphinx.pocoo.org, for more information). If not given, the Makefile.am
79       and the conf.py (sphinx configuration) files are generated. In this case, the file name extension of source files must be .rst.
80       See small examples in Examples/pygui1 and Examples/cppgui1.
81    :param gui: can be used to add a GUI to the module. It must be a list of file names (sources, images, qt designer files, ...).
82       If not given, the Makefile.am and SalomeApp.xml are generated. All image files are put in the resources directory of the module.
83       The GUI can be implemented in C++ (file name extension '.cxx') or in Python (file name extension '.py').
84       See small examples in Examples/pygui1 and Examples/cppgui1.
85
86    For example, the following call defines a module named "mymodule" with 2 components c1 and c2  (they must have been
87    defined before) that will be installed in the "install" directory::
88
89       >>> m = module_generator.Module('mymodule', components=[c1,c2],
90                                                   prefix="./install")
91
92   """
93   def __init__(self, name, components=None, prefix="",layout="multidir", doc=None, gui=None):
94     self.name = name
95     self.components = components or []
96     self.prefix = prefix or "%s_INSTALL" % name
97     self.layout=layout
98     self.doc = doc
99     self.gui = gui
100     try:
101       self.validate()
102     except Invalid,e:
103       if debug:
104         traceback.print_exc()
105       print "Error in module %s: %s" % (name,e)
106       raise SystemExit
107
108   def validate(self):
109     # Test Module name, canot have a "-" in the name
110     if self.name.find("-") != -1:
111       raise Invalid("Module name %s is not valid, remove character - in the module name" % self.name)
112     lcompo = set()
113     for compo in self.components:
114       if compo.name in lcompo:
115         raise Invalid("%s is already defined as a component of the module" % compo.name)
116       lcompo.add(compo.name)
117       compo.validate()
118     if self.gui and self.layout != "multidir":
119       raise Invalid("A module with GUI can not be generated if layout is not multidir")
120
121 class Component(object):
122   def __init__(self, name, services=None, impl="PY", libs="", rlibs="",
123                      includes="", kind="lib", sources=None,
124                      inheritedclass="",compodefs="",
125                      idls=None,interfacedefs="",inheritedinterface="",addedmethods=""):
126     self.name = name
127     self.impl = impl
128     self.kind = kind
129     self.services = services or []
130     self.libs = libs
131     self.rlibs = rlibs
132     self.includes = includes
133     self.sources = sources or []
134     self.inheritedclass=inheritedclass
135     self.compodefs=compodefs
136     self.idls=idls
137     self.interfacedefs=interfacedefs
138     self.inheritedinterface=inheritedinterface
139     self.addedmethods=addedmethods
140
141   def validate(self):
142     if self.impl not in ValidImpl:
143       raise Invalid("%s is not a valid implementation. It should be one of %s" % (self.impl, ValidImpl))
144
145     lnames = set()
146     for serv in self.services:
147       serv.impl = self.impl
148       if serv.name in lnames:
149         raise Invalid("%s is already defined as a service of the module" % serv.name)
150       lnames.add(serv.name)
151       serv.validate()
152
153     for src in self.sources:
154       if not os.path.exists(src):
155         raise Invalid("Source file %s does not exist" % src)
156
157   def getImpl(self):
158     return "SO", ""
159
160   def getMakefileItems(self,gen):
161     return {}
162
163   def setPrerequisites(self, prerequisites_file):
164     self.prerequisites = prerequisites_file
165
166 class Service(object):
167   """
168    A :class:`Service` instance represents a component service with dataflow and datastream ports.
169
170    :param name: gives the name of the service.
171    :type name: str
172    :param inport: gives the list of input dataflow ports.
173    :param outport: gives the list of output dataflow ports. An input or output dataflow port is defined
174       by a 2-tuple (port name, data type name). The list of supported basic data types is: "double", "long", "string",
175       "dblevec", "stringvec", "intvec", "file" and "pyobj" only for Python services. Depending on the implementation
176       language, it is also possible to use some types from SALOME modules (see :ref:`yacstypes`).
177    :param ret: gives the type of the return parameter
178    :param instream: gives the list of input datastream ports.
179    :param outstream: gives the list of output datastream ports. An input or output datastream port is defined
180       by a 3-tuple (port name, data type name, mode name). The list of possible data types is: "CALCIUM_double", "CALCIUM_integer",
181       "CALCIUM_real", "CALCIUM_string", "CALCIUM_complex", "CALCIUM_logical", "CALCIUM_long". The mode can be "I" (iterative mode)
182       or "T" (temporal mode).
183    :param defs: gives the source code to insert in the definition section of the component. It can be C++ includes
184       or Python imports
185    :type defs: str
186    :param body: gives the source code to insert in the service call. It can be any C++
187       or Python code that fits well in the body of the service method.
188    :type body: str
189
190    For example, the following call defines a minimal Python service with one input dataflow port (name "a", type double)
191    and one input datastream port::
192
193       >>> s1 = module_generator.Service('myservice', inport=[("a","double"),],
194                                         instream=[("aa","CALCIUM_double","I")],
195                                         body="print a")
196
197
198   """
199   def __init__(self, name, inport=None, outport=None, ret="void", instream=None, outstream=None,
200                      parallel_instream=None, parallel_outstream=None, defs="", body="", impl_type="sequential"):
201     self.name = name
202     self.inport = inport or []
203     self.outport = outport or []
204     self.ret = ret
205     self.instream = instream or []
206     self.outstream = outstream or []
207     self.parallel_instream = parallel_instream or []
208     self.parallel_outstream = parallel_outstream or []
209     self.defs = defs
210     self.body = body
211     self.impl = ""
212     self.impl_type = impl_type
213
214   def validate(self):
215     lports = set()
216     for port in self.inport:
217       name, typ = self.validatePort(port)
218       if name in lports:
219         raise Invalid("%s is already defined as a service parameter" % name)
220       lports.add(name)
221
222     for port in self.outport:
223       name, typ = self.validatePort(port)
224       if name in lports:
225         raise Invalid("%s is already defined as a service parameter" % name)
226       lports.add(name)
227
228     lports = set()
229     for port in self.instream:
230       name, typ, dep = self.validateStream(port)
231       if name in lports:
232         raise Invalid("%s is already defined as a stream port" % name)
233       lports.add(name)
234
235     for port in self.outstream:
236       name, typ, dep = self.validateStream(port)
237       if name in lports:
238         raise Invalid("%s is already defined as a stream port" % name)
239       lports.add(name)
240
241     for port in self.parallel_instream:
242       name, typ = self.validateParallelStream(port)
243       if name in lports:
244         raise Invalid("%s is already defined as a stream port" % name)
245       lports.add(name)
246
247     for port in self.parallel_outstream:
248       name, typ = self.validateParallelStream(port)
249       if name in lports:
250         raise Invalid("%s is already defined as a stream port" % name)
251       lports.add(name)
252
253     self.validateImplType()
254
255   def validatePort(self, port):
256     try:
257       name, typ = port
258     except:
259       raise Invalid("%s is not a valid definition of an data port (name,type)" % (port,))
260
261     if self.impl in ("PY", "ASTER"):
262       validtypes = PyValidTypes
263     else:
264       validtypes = ValidTypes
265
266     if typ not in validtypes:
267       raise Invalid("%s is not a valid type. It should be one of %s" % (typ, validtypes))
268     return name, typ
269
270   def validateImplType(self):
271     if self.impl_type not in ValidImplTypes:
272       raise Invalid("%s is not a valid impl type. It should be one of %s" % (self.impl_type, ValidImplTypes))
273
274   def validateStream(self, port):
275     try:
276       name, typ, dep = port
277     except:
278       raise Invalid("%s is not a valid definition of a stream port (name,type,dependency)" % (port,))
279     if typ not in ValidStreamTypes:
280       raise Invalid("%s is not a valid type. It should be one of %s" % (typ, ValidStreamTypes))
281     if dep not in ValidDependencies:
282       raise Invalid("%s is not a valid dependency. It should be one of %s" % (dep, ValidDependencies))
283     return name, typ, dep
284
285   def validateParallelStream(self, port):
286     try:
287       name, typ = port
288     except:
289       raise Invalid("%s is not a valid definition of a parallel stream port (name,type)" % (port,))
290     if typ not in ValidParallelStreamTypes:
291       raise Invalid("%s is not a valid type. It should be one of %s" % (typ, ValidParallelStreamTypes))
292     return name, typ
293
294 class Generator(object):
295   """
296    A :class:`Generator` instance take a :class:`Module` instance as its first parameter and can be used to generate the
297    SALOME source module, builds it, installs it and includes it in a SALOME application.
298
299    :param module: gives the :class:`Module` instance that will be used for the generation.
300    :param context: If given , its content is used to specify the prerequisites
301       environment file (key *"prerequisites"*) and the SALOME KERNEL installation directory (key *"kernel"*).
302    :type context: dict
303
304    For example, the following call creates a generator for the module m::
305
306       >>> g = module_generator.Generator(m,context)
307   """
308   def __init__(self, module, context=None):
309     self.module = module
310     self.context = context or {}
311     self.kernel = self.context["kernel"]
312     self.gui = self.context.get("gui")
313     self.makeflags = self.context.get("makeflags")
314     self.aster = ""
315     if self.module.gui and not self.gui:
316       raise Invalid("To generate a module with GUI, you need to set the 'gui' parameter in the context dictionnary")
317     for component in self.module.components:
318       component.setPrerequisites(self.context.get("prerequisites"))
319
320   def generate(self):
321     """Generate a SALOME source module"""
322     module = self.module
323     namedir = module.name+"_SRC"
324     force = self.context.get("force")
325     update = self.context.get("update")
326     paco = self.context.get("paco")
327     if os.path.exists(namedir):
328       if force:
329         shutil.rmtree(namedir)
330       elif not update:
331         raise Invalid("The directory %s already exists" % namedir)
332     if update:
333       makedirs(namedir)
334     else:
335       os.makedirs(namedir)
336
337     srcs = {}
338     makefile = "SUBDIRS="
339     makefileItems={"header":"""
340 include $(top_srcdir)/adm_local/make_common_starter.am
341 AM_CFLAGS=$(SALOME_INCLUDES) -fexceptions
342 """,
343                    "salomepython_PYTHON":[],
344                    "dist_salomescript_SCRIPTS":[],
345                    "salomeres_DATA":[],
346                    "lib_LTLIBRARIES":[],
347                    "salomeinclude_HEADERS":[],
348                    "body":"",
349                   }
350
351     #get the list of SALOME modules used and put it in used_modules attribute
352     def get_dependent_modules(mod,modules):
353       modules[mod]=1
354       if not salome_modules[mod].has_key("depends"):return
355       for m in salome_modules[mod]["depends"]:
356         if modules.has_key(m):continue
357         get_dependent_modules(m,modules)
358
359     modules = {}
360     for compo in module.components:
361       for serv in compo.services:
362         for name, typ in serv.inport + serv.outport + [ ("return",serv.ret) ] :
363           mod = moduleTypes[typ]
364           if mod:
365             get_dependent_modules(mod,modules)
366
367     self.used_modules = modules.keys()
368
369     for compo in module.components:
370       #for components files
371       fdict=compo.makeCompo(self)
372       if self.module.layout=="multidir":
373         srcs[compo.name] = fdict
374         #for src/Makefile.am
375         makefile = makefile + " " + compo.name
376       else:
377         srcs.update(fdict)
378         #for src/Makefile.am
379         mdict=compo.getMakefileItems(self)
380         makefileItems["salomepython_PYTHON"]=makefileItems["salomepython_PYTHON"]+mdict.get("salomepython_PYTHON",[])
381         makefileItems["dist_salomescript_SCRIPTS"]=makefileItems["dist_salomescript_SCRIPTS"]+mdict.get("dist_salomescript_SCRIPTS",[])
382         makefileItems["salomeres_DATA"]=makefileItems["salomeres_DATA"]+mdict.get("salomeres_DATA",[])
383         makefileItems["lib_LTLIBRARIES"]=makefileItems["lib_LTLIBRARIES"]+mdict.get("lib_LTLIBRARIES",[])
384         makefileItems["salomeinclude_HEADERS"]=makefileItems["salomeinclude_HEADERS"]+mdict.get("salomeinclude_HEADERS",[])
385         makefileItems["body"]=makefileItems["body"]+mdict.get("body","")+'\n'
386
387     if module.gui:
388       GUIname=module.name+"GUI"
389       fdict=self.makeGui(namedir)
390       srcs[GUIname] = fdict
391       #for src/Makefile.am
392       makefile = makefile + " " + GUIname
393
394     if self.module.layout == "multidir":
395       srcs["Makefile.am"] = makefile+'\n'
396     else:
397       srcs["Makefile.am"] = self.makeMakefile(makefileItems)
398
399     docsubdir=""
400     if module.doc:
401       docsubdir="doc"
402
403     #for catalog files
404     catalogfile = "%sCatalog.xml" % module.name
405
406     need_boost=0
407     if module.gui:
408         need_boost=1
409     for compo in module.components:
410       if hasattr(compo,"calciumextendedinterface") and compo.calciumextendedinterface:
411         need_boost=1
412         break
413
414     #add makefile definitions to make_common_starter.am
415     other_includes=""
416     common_starter = makecommon.substitute(other_includes=other_includes)
417     for mod in self.used_modules:
418       common_starter = common_starter + salome_modules[mod]["makefiledefs"] + '\n'
419
420     adm_local={"make_common_starter.am": common_starter, "check_aster.m4":check_aster}
421     if module.doc:
422       adm_local["check_sphinx.m4"]=check_sphinx
423
424     self.makeFiles({"autogen.sh":autogen,
425                     "Makefile.am":mainMakefile.substitute(docsubdir=docsubdir),
426                     "README":"", "NEWS":"", "AUTHORS":"", "ChangeLog":"",
427                     "src":srcs,
428                     "resources":{"Makefile.am":resMakefile.substitute(module=module.name), catalogfile:self.makeCatalog()},
429                     "adm_local":adm_local,
430                     }, namedir)
431
432     #add checks for modules in configure.ac
433     configure_modules=""
434     for mod in self.used_modules:
435       configure_modules = configure_modules + salome_modules[mod]["configdefs"] + '\n'
436
437     #for configure.ac
438     configure_makefiles = []
439     if self.module.layout=="multidir":
440       for compo in module.components:
441         configure_makefiles.append("     src/"+compo.name+"/Makefile")
442
443     if module.gui:
444       configure_makefiles.append("     src/%sGUI/Makefile" % module.name)
445     if module.doc:
446       configure_makefiles.append("     doc/Makefile")
447
448     other_check=""
449     other_summary=""
450     other_require=""
451
452     if need_boost:
453       other_check=other_check+"""CHECK_BOOST
454 """
455       other_summary=other_summary+"""echo "  Boost  ................. : $boost_ok"
456 """
457
458     if module.gui:
459       other_check=other_check + """CHECK_SALOME_GUI
460 CHECK_QT
461 """
462       other_summary=other_summary+'''echo "  SALOME GUI ............. : $SalomeGUI_ok"
463 echo "  Qt ..................... : $qt_ok"
464 '''
465       other_require=other_require + """
466       if test "x$SalomeGUI_ok" = "xno"; then
467         AC_MSG_ERROR([SALOME GUI is required],1)
468       fi
469       if test "x$qt_ok" = "xno"; then
470         AC_MSG_ERROR([Qt library is required],1)
471       fi
472 """
473     if module.doc:
474       other_check=other_check+"CHECK_SPHINX\n"
475       other_summary=other_summary+'''echo "  Sphinx ................. : $sphinx_ok"\n'''
476       other_require=other_require + """
477       if test "x$sphinx_ok" = "xno"; then
478         AC_MSG_ERROR([Sphinx documentation generator is required],1)
479       fi
480 """
481
482     files={}
483     #for idl files
484     idlfile = "%s.idl" % module.name
485     paco_config=""
486     PACO_BUILT_SOURCES=""
487     PACO_SALOMEINCLUDE_HEADERS=""
488     PACO_INCLUDES=""
489     PACO_salomepython_DATA=""
490     PACO_salomeidl_DATA=""
491
492     if paco:
493       PACO_BUILT_SOURCES = idlMakefilePaCO_BUILT_SOURCES.substitute(module=module.name)
494       PACO_SALOMEINCLUDE_HEADERS = idlMakefilePaCO_nodist_salomeinclude_HEADERS.substitute(module=module.name)
495       PACO_salomepython_DATA = idlMakefilePACO_salomepython_DATA.substitute(module=module.name)
496       PACO_salomeidl_DATA = idlMakefilePACO_salomeidl_DATA.substitute(module=module.name)
497       PACO_INCLUDES = idlMakefilePACO_INCLUDES
498       paco_config=paco_configure
499
500     files["configure.ac"]=configure.substitute(module=module.name.lower(),
501                                                makefiles='\n'.join(configure_makefiles),
502                                                paco_configure=paco_config,
503                                                modules=configure_modules,
504                                                other_check=other_check,
505                                                other_summary=other_summary,
506                                                other_require=other_require,
507                                               )
508
509     #if components have other idls
510     other_idls=""
511     other_sks=""
512     for compo in module.components:
513       if compo.idls:
514         for idl in compo.idls:
515           for fidl in glob.glob(idl):
516             other_idls=other_idls+os.path.basename(fidl) +" "
517             other_sks=other_sks+os.path.splitext(os.path.basename(fidl))[0]+"SK.cc "
518
519     idlfiles={"Makefile.am":    idlMakefile.substitute(module=module.name,
520                                                        PACO_BUILT_SOURCES=PACO_BUILT_SOURCES,
521                                                        PACO_SALOMEINCLUDE_HEADERS=PACO_SALOMEINCLUDE_HEADERS,
522                                                        PACO_INCLUDES=PACO_INCLUDES,
523                                                        PACO_salomepython_DATA=PACO_salomepython_DATA,
524                                                        PACO_salomeidl_DATA=PACO_salomeidl_DATA,
525                                                        other_idls=other_idls,other_sks=other_sks,
526                                                        ),
527               idlfile : self.makeidl(),
528              }
529     if paco:
530       idlfiles["%s.xml" % module.name]=self.makexml()
531
532     files["idl"]=idlfiles
533
534     self.makeFiles(files,namedir)
535
536     os.chmod(os.path.join(namedir, "autogen.sh"), 0777)
537     #copy source files if any in created tree
538     for compo in module.components:
539       for src in compo.sources:
540         if self.module.layout=="multidir":
541           shutil.copyfile(src, os.path.join(namedir, "src", compo.name, os.path.basename(src)))
542         else:
543           shutil.copyfile(src, os.path.join(namedir, "src", os.path.basename(src)))
544
545       if compo.idls:
546         #copy provided idl files in idl directory
547         for idl in compo.idls:
548           for fidl in glob.glob(idl):
549             shutil.copyfile(fidl, os.path.join(namedir, "idl", os.path.basename(fidl)))
550
551     checks= ("check_Kernel.m4", "check_omniorb.m4", "ac_linker_options.m4", "ac_cxx_option.m4",
552              "python.m4", "enable_pthreads.m4", "check_f77.m4", "acx_pthread.m4", "check_paco++.m4",
553              "check_mpi.m4", "check_lam.m4", "check_openmpi.m4", "check_mpich.m4")
554     if need_boost:
555       checks=checks+("check_boost.m4",)
556     for m4file in checks:
557       shutil.copyfile(os.path.join(self.kernel, "salome_adm", "unix", "config_files", m4file),
558                       os.path.join(namedir, "adm_local", m4file))
559
560     if self.module.gui:
561       for m4file in ("check_GUI.m4", "check_qt.m4", "check_opengl.m4"):
562         shutil.copyfile(os.path.join(self.gui, "adm_local", "unix", "config_files", m4file),
563                         os.path.join(namedir, "adm_local", m4file))
564
565     self.makeDoc(namedir)
566     return
567
568   def makeDoc(self,namedir):
569     if not self.module.doc:
570       return
571     rep=os.path.join(namedir,"doc")
572     os.makedirs(rep)
573     for docs in self.module.doc:
574       for doc in glob.glob(docs):
575         name = os.path.basename(doc)
576         shutil.copyfile(doc, os.path.join(rep, name))
577
578     d={}
579
580     others=""
581     if not self.module.gui:
582        #without gui but with doc: create a small SalomeApp.xml in doc directory
583        if not os.path.exists(os.path.join(namedir, "doc", "SalomeApp.xml")):
584          #create a minimal SalomeApp.xml
585          salomeapp=docsalomeapp.substitute(module=self.module.name,lmodule=self.module.name.lower())
586          d["SalomeApp.xml"]=salomeapp
587        others="SalomeApp.xml"
588
589     if not os.path.exists(os.path.join(namedir, "doc", "Makefile.am")):
590       #create a minimal makefile.am
591       d["Makefile.am"]=docmakefile.substitute(others=others)
592
593     if not os.path.exists(os.path.join(namedir, "doc", "conf.py")):
594       #create a minimal conf.py
595       d["conf.py"]=docconf.substitute(module=self.module.name)
596
597     self.makeFiles(d,os.path.join(namedir,"doc"))
598
599   def makeGui(self,namedir):
600     if not self.module.gui:
601       return
602     ispython=False
603     iscpp=False
604     #Force creation of intermediate directories
605     os.makedirs(os.path.join(namedir, "src", self.module.name+"GUI"))
606
607     for srcs in self.module.gui:
608       for src in glob.glob(srcs):
609         shutil.copyfile(src, os.path.join(namedir, "src", self.module.name+"GUI", os.path.basename(src)))
610         if src[-3:]==".py":ispython=True
611         if src[-4:]==".cxx":iscpp=True
612     if ispython and iscpp:
613       raise Invalid("Module GUI must be pure python or pure C++ but not mixed")
614     if ispython:
615       return self.makePyGUI(namedir)
616     if iscpp:
617       return self.makeCPPGUI(namedir)
618     raise Invalid("Module GUI must be in python or C++ but it is none of them")
619
620   def makePyGUI(self,namedir):
621     d={}
622     if not os.path.exists(os.path.join(namedir, "src", self.module.name+"GUI", "Makefile.am")):
623       #create a minimal makefile.am
624       sources=[]
625       other=[]
626       for srcs in self.module.gui:
627         for src in glob.glob(srcs):
628           if src[-3:]==".py":
629             sources.append(os.path.basename(src))
630           else:
631             other.append(os.path.basename(src))
632       makefile=pyguimakefile.substitute(sources=" ".join(sources),other_sources=" ".join(other))
633       d["Makefile.am"]=makefile
634
635     if not os.path.exists(os.path.join(namedir, "src", self.module.name+"GUI", "SalomeApp.xml")):
636       #create a minimal SalomeApp.xml
637       salomeapp=pysalomeapp.substitute(module=self.module.name,lmodule=self.module.name.lower())
638       d["SalomeApp.xml"]=salomeapp
639
640     return d
641
642   def makeCPPGUI(self,namedir):
643     d={}
644     if not os.path.exists(os.path.join(namedir, "src", self.module.name+"GUI", "Makefile.am")):
645       #create a minimal makefile.am
646       sources=[]
647       other=[]
648       ui_files=[]
649       for srcs in self.module.gui:
650         for src in glob.glob(srcs):
651           if src[-4:]==".cxx":
652             sources.append(os.path.basename(src))
653           elif src[-2:]==".h":
654             sources.append(os.path.basename(src)[:-2]+"_moc.cxx")
655           elif src[-3:]==".ui":
656             ui_files.append("ui_"+os.path.basename(src)[:-3]+".h")
657           elif src[-3:]==".ts":
658             other.append(os.path.basename(src)[:-3]+".qm")
659           else:
660             other.append(os.path.basename(src))
661
662       makefile=cppguimakefile.substitute(sources=" ".join(sources),other_sources=" ".join(other),
663                                          module=self.module.name, uisources= " ".join(ui_files))
664       d["Makefile.am"]=makefile
665
666     if not os.path.exists(os.path.join(namedir, "src", self.module.name+"GUI", "SalomeApp.xml")):
667       #create a minimal SalomeApp.xml
668       salomeapp=cppsalomeapp.substitute(module=self.module.name,lmodule=self.module.name.lower())
669       d["SalomeApp.xml"]=salomeapp
670
671     return d
672
673   def makeMakefile(self,makefileItems):
674     makefile=""
675     if makefileItems.has_key("header"):
676       makefile=makefile + makefileItems["header"]+'\n'
677     if makefileItems.has_key("lib_LTLIBRARIES"):
678       makefile=makefile+"lib_LTLIBRARIES= "+" ".join(makefileItems["lib_LTLIBRARIES"])+'\n'
679     if makefileItems.has_key("salomepython_PYTHON"):
680       makefile=makefile+"salomepython_PYTHON= "+" ".join(makefileItems["salomepython_PYTHON"])+'\n'
681     if makefileItems.has_key("dist_salomescript_SCRIPTS"):
682       makefile=makefile+"dist_salomescript_SCRIPTS= "+" ".join(makefileItems["dist_salomescript_SCRIPTS"])+'\n'
683     if makefileItems.has_key("salomeres_DATA"):
684       makefile=makefile+"salomeres_DATA= "+" ".join(makefileItems["salomeres_DATA"])+'\n'
685     if makefileItems.has_key("salomeinclude_HEADERS"):
686       makefile=makefile+"salomeinclude_HEADERS= "+" ".join(makefileItems["salomeinclude_HEADERS"])+'\n'
687     if makefileItems.has_key("body"):
688       makefile=makefile+makefileItems["body"]+'\n'
689     return makefile
690
691   def makeArgs(self, service):
692     """generate source service for arguments"""
693     params = []
694     for name, typ in service.inport:
695       if typ=="file":continue #files are not passed through service interface
696       params.append("%s %s" % (corba_in_type(typ, self.module.name), name))
697     for name, typ in service.outport:
698       if typ=="file":continue #files are not passed through service interface
699       params.append("%s %s" % (corba_out_type(typ, self.module.name), name))
700     return ",".join(params)
701
702   def makeCatalog(self):
703     """generate SALOME components catalog source"""
704     components = []
705     for compo in self.module.components:
706       services = []
707       for serv in compo.services:
708         params = []
709         for name, typ in serv.inport:
710           params.append(cataInparam.substitute(name=name, type=typ))
711         inparams = "\n".join(params)
712         params = []
713         for name, typ in serv.outport:
714           params.append(cataOutparam.substitute(name=name, type=typ))
715         if serv.ret != "void" :
716           params.append(cataOutparam.substitute(name="return", type=serv.ret))
717         outparams = "\n".join(params)
718         streams = []
719         for name, typ, dep in serv.instream:
720           streams.append(cataInStream.substitute(name=name, type=calciumTypes[typ], dep=dep))
721         for name, typ, dep in serv.outstream:
722           streams.append(cataOutStream.substitute(name=name, type=calciumTypes[typ], dep=dep))
723         for name, typ in serv.parallel_instream:
724           streams.append(cataInParallelStream.substitute(name=name, type=DatastreamParallelTypes[typ]))
725         for name, typ in serv.parallel_outstream:
726           streams.append(cataOutParallelStream.substitute(name=name, type=DatastreamParallelTypes[typ]))
727         datastreams = "\n".join(streams)
728         services.append(cataService.substitute(service=serv.name, author="EDF-RD",
729                                                inparams=inparams, outparams=outparams, datastreams=datastreams))
730       impltype, implname = compo.getImpl()
731       components.append(cataCompo.substitute(component=compo.name, author="EDF-RD", impltype=impltype, implname=implname,
732                                              services='\n'.join(services)))
733     return catalog.substitute(components='\n'.join(components))
734
735   def makeidl(self):
736     """generate module IDL file source (CORBA interface)"""
737     from pacocompo import PACOComponent
738     interfaces = []
739     idldefs=""
740     for compo in self.module.components:
741       if isinstance(compo, PACOComponent):
742         services = []
743         for serv in compo.services:
744           params = []
745           for name, typ in serv.inport:
746             if typ == "file":continue #files are not passed through IDL interface
747             params.append("in %s %s" % (idlTypes[typ], name))
748           for name, typ in serv.outport:
749             if typ == "file":continue #files are not passed through IDL interface
750             params.append("out %s %s" % (idlTypes[typ], name))
751           service = "    void %s(" % serv.name
752           service = service+",".join(params)+");"
753           services.append(service)
754
755         interfaces.append(parallel_interface.substitute(component=compo.name, services="\n".join(services)))
756
757       else:
758         services = []
759         for serv in compo.services:
760           params = []
761           for name, typ in serv.inport:
762             if typ == "file":continue #files are not passed through IDL interface
763             if compo.impl in ("PY", "ASTER") and typ == "pyobj":
764               typ = "Engines::fileBlock"
765             else:
766               typ=idlTypes[typ]
767             params.append("in %s %s" % (typ, name))
768           for name, typ in serv.outport:
769             if typ == "file":continue #files are not passed through IDL interface
770             if compo.impl in ("PY", "ASTER") and typ == "pyobj":
771               typ = "Engines::fileBlock"
772             else:
773               typ=idlTypes[typ]
774             params.append("out %s %s" % (typ, name))
775           service = "    %s %s(" % (idlTypes[serv.ret],serv.name)
776           service = service+",".join(params)+") raises (SALOME::SALOME_Exception);"
777           services.append(service)
778
779         from hxxcompo import HXX2SALOMEComponent
780         from hxxparacompo import HXX2SALOMEParaComponent
781         if isinstance(compo,HXX2SALOMEComponent) or isinstance(compo,HXX2SALOMEParaComponent):
782           from hxx_tmpl import interfaceidlhxx
783           Inherited=""
784           if isinstance(compo,HXX2SALOMEParaComponent):
785               Inherited="SALOME_MED::ParaMEDMEMComponent"
786               idldefs="""#include "ParaMEDMEMComponent.idl"\n"""
787           else:
788               if compo.use_medmem==True:
789                   Inherited="Engines::EngineComponent,SALOME::MultiCommClass,SALOME_MED::MED_Gen_Driver"
790               else:
791                   Inherited="Engines::EngineComponent"
792           interfaces.append(interfaceidlhxx.substitute(component=compo.name,inherited=Inherited, services="\n".join(services)))
793         else:
794           inheritedinterface=""
795           if compo.inheritedinterface:
796             inheritedinterface=compo.inheritedinterface+","
797           interfaces.append(interface.substitute(component=compo.name, services="\n".join(services),inheritedinterface=inheritedinterface))
798
799     #build idl includes for SALOME modules
800     for mod in self.used_modules:
801       idldefs = idldefs + salome_modules[mod]["idldefs"]
802
803     for compo in self.module.components:
804       if compo.interfacedefs:
805         idldefs = idldefs + compo.interfacedefs
806
807     return idl.substitute(module=self.module.name, interfaces='\n'.join(interfaces),idldefs=idldefs)
808
809   # For PaCO++
810   def makexml(self):
811     from pacocompo import PACOComponent
812     interfaces = []
813     for compo in self.module.components:
814       if isinstance(compo, PACOComponent):
815         services = []
816         for serv in compo.services:
817           if serv.impl_type == "parallel":
818             service = xml_service.substitute(service_name=serv.name)
819             services.append(service)
820         interfaces.append(xml_interface.substitute(component=compo.name, xml_services="\n".join(services)))
821     return xml.substitute(module=self.module.name, interfaces='\n'.join(interfaces))
822
823   def makeFiles(self, dic, basedir):
824     """create files and directories defined in dictionary dic in basedir directory
825        dic key = file name to create
826        dic value = file content or dictionary defining the content of a sub directory
827     """
828     for name, content in dic.items():
829       filename = os.path.join(basedir, name)
830       if isinstance(content, str):
831         fil =  open(filename, 'w')
832         fil.write(content)
833         fil.close()
834       else:
835         if not os.path.exists(filename):
836           os.makedirs(filename)
837         self.makeFiles(content, filename)
838
839   def bootstrap(self):
840     """Execute the first build step (bootstrap autotools with autogen.sh script) : execution of libtool, autoconf, automake"""
841     ier = os.system("cd %s_SRC;sh autogen.sh" % self.module.name)
842     if ier != 0:
843       raise Invalid("bootstrap has ended in error")
844
845   def configure(self):
846     """Execute the second build step (configure) with installation prefix as given by the prefix attribute of module"""
847     prefix = self.module.prefix
848     paco = self.context.get("paco")
849     mpi = self.context.get("mpi")
850     args = (self.module.name, self.kernel, self.aster)
851     cmd = "cd %s_SRC;./configure --with-kernel=%s --with-aster=%s" % args
852     if self.gui:
853       cmd = cmd + " --with-gui=%s" % self.gui
854     if prefix:
855       prefix = os.path.abspath(prefix)
856       cmd = cmd + " --prefix=%s" % prefix
857     if paco:
858       cmd += " --with-paco=%s" % paco
859     if mpi:
860       cmd += " --with-mpi=%s" % mpi
861
862     ier = os.system(cmd)
863     if ier != 0:
864       raise Invalid("configure has ended in error")
865
866   def make(self):
867     """Execute the third build step (compile and link) : make"""
868     make_command = "make "
869     if self.makeflags:
870       make_command += self.makeflags
871     ier = os.system("cd %s_SRC;%s" % (self.module.name, make_command))
872     if ier != 0:
873       raise Invalid("make has ended in error")
874
875   def install(self):
876     """Execute the installation step : make install """
877     makedirs(self.module.prefix)
878     ier = os.system("cd %s_SRC;make install" % self.module.name)
879     if ier != 0:
880       raise Invalid("install has ended in error")
881
882   def make_appli(self, appliname, restrict=None, altmodules=None, resources=""):
883     """
884    Create a SALOME application containing the module and preexisting SALOME modules.
885
886    :param appliname: is a string that gives the name of the application (directory path where the application
887       will be installed).
888    :type appliname: str
889    :param restrict: If given (a list of module names), only those SALOME modules will be included in the
890       application. The default is to include all modules that are located in the same directory as the KERNEL module and have
891       the same suffix (for example, if KERNEL directory is KERNEL_V5 and GEOM directory is GEOM_V5, GEOM module is automatically
892       included, except if restrict is used).
893    :param altmodules: can be used to add SALOME modules that cannot be managed with the precedent rule. This parameter
894       is a dict with a module name as the key and the installation path as the value.
895    :param resources: can be used to define an alternative resources catalog (path of the file).
896
897    For example, the following calls create a SALOME application with external modules and resources catalog in "appli" directory::
898
899      >>> g=Generator(m,context)
900      >>> g.generate()
901      >>> g.bootstrap()
902      >>> g.configure()
903      >>> g.make()
904      >>> g.install()
905      >>> g.make_appli("appli", restrict=["KERNEL"], altmodules={"GUI":GUI_ROOT_DIR, "YACS":YACS_ROOT_DIR},
906                       resources="myresources.xml")
907
908     """
909     makedirs(appliname)
910
911     rootdir, kerdir = os.path.split(self.kernel)
912
913     #collect modules besides KERNEL module with the same suffix if any
914     modules_dict = {}
915     if kerdir[:6] == "KERNEL":
916       suffix = kerdir[6:]
917       for mod in os.listdir(rootdir):
918         if mod[-len(suffix):] == suffix:
919           module = mod[:-len(suffix)]
920           path = os.path.join(rootdir, mod)
921           #try to find catalog files
922           lcata = glob.glob(os.path.join(path, "share", "salome", "resources", "*", "*Catalog.xml"))
923           if not lcata:
924             #catalogs have not been found : try the upper level
925             lcata = glob.glob(os.path.join(path, "share", "salome", "resources", "*Catalog.xml"))
926           if lcata:
927             #catalogs have been found : add the corresponding entries in the application
928             for cata in lcata:
929               catadir, catafile = os.path.split(cata)
930               name = catafile[:-11]
931               modules_dict[name] = '  <module name="%s" path="%s"/>' % (name, path)
932           else:
933             modules_dict[module] = '  <module name="%s" path="%s"/>' % (module, path)
934
935     modules_dict["KERNEL"] = '  <module name="KERNEL" path="%s"/>' % self.kernel
936
937     #keep only the modules which names are in restrict if given
938     modules = []
939     if restrict:
940       for mod in restrict:
941         if modules_dict.has_key(mod):
942           modules.append(modules_dict[mod])
943     else:
944       modules = modules_dict.values()
945
946     #add the alternate modules if given
947     if altmodules:
948       for module, path in altmodules.items():
949         modules.append('  <module name="%s" path="%s"/>' % (module, path))
950
951     #add the generated module
952     modules.append('  <module name="%s" path="%s"/>' % (self.module.name, os.path.abspath(self.module.prefix)))
953
954
955     #try to find a prerequisites file
956     prerequisites = self.context.get("prerequisites")
957     if not prerequisites:
958       #try to find one in rootdir
959       prerequisites = os.path.join(rootdir, "profile%s.sh" % suffix)
960     if not os.path.exists(prerequisites):
961       raise Invalid("Can not create an application : prerequisites file not defined or does not exist")
962
963     #add resources catalog if it exists
964     resources_spec=""
965     if os.path.isfile(resources):
966       resources_spec='<resources path="%s" />' % os.path.abspath(resources)
967
968     #create config_appli.xml file
969     appli = application.substitute(prerequisites=prerequisites,
970                                    modules="\n".join(modules),
971                                    resources=resources_spec)
972     fil = open(os.path.join(appliname, "config_appli.xml"), 'w')
973     fil.write(appli)
974     fil.close()
975
976     #execute appli_gen.py script
977     appligen = os.path.join(self.kernel, "bin", "salome", "appli_gen.py")
978     ier = os.system("cd %s;%s" % (appliname, appligen))
979     if ier != 0:
980       raise Invalid("make_appli has ended in error")
981
982     #add CatalogResources.xml if not created by appli_gen.py
983     if not os.path.exists(os.path.join(appliname, "CatalogResources.xml")):
984       #CatalogResources.xml does not exist create a minimal one
985       fil  = open(os.path.join(appliname, 'CatalogResources.xml'), 'w')
986       command = """<!DOCTYPE ResourcesCatalog>
987 <resources>
988     <machine hostname="%s" protocol="ssh" mode="interactive" />
989 </resources>
990 """
991       host = socket.gethostname().split('.')[0]
992       fil.write(command % host)
993       fil.close()
994