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