Salome HOME
Add generated modules in EDF appli for SALOME 6.6.0
[tools/sat.git] / commands / application.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3 #  Copyright (C) 2010-2013  CEA/DEN
4 #
5 #  This library is free software; you can redistribute it and/or
6 #  modify it under the terms of the GNU Lesser General Public
7 #  License as published by the Free Software Foundation; either
8 #  version 2.1 of the License.
9 #
10 #  This library is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 #  Lesser General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Lesser General Public
16 #  License along with this library; if not, write to the Free Software
17 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18
19 import os
20 import stat
21 import sys
22 import shutil
23 import subprocess
24 import getpass
25
26 from src import ElementTree as etree
27 import src
28
29 parser = src.options.Options()
30 parser.add_option('n', 'name', 'string', 'name',
31     _('Optional: The name of the application (default is APPLI.name or '
32       'runAppli)'))
33 parser.add_option('c', 'catalog', 'string', 'catalog',
34     _('Optional: The resources catalog to use'))
35 parser.add_option('t', 'target', 'string', 'target',
36     _('Optional: The directory where to create the application (default is '
37       'APPLICATION.workdir)'))
38 parser.add_option('', 'gencat', 'string', 'gencat',
39     _("Optional: Create a resources catalog for the specified machines "
40       "(separated with ',')\n\tNOTICE: this command will ssh to retrieve "
41       "information to each machine in the list"))
42 parser.add_option('m', 'module', 'list2', 'modules',
43     _("Optional: the restricted list of module(s) to include in the "
44       "application"))
45
46 ##
47 # Creates an alias for runAppli.
48 def make_alias(appli_path, alias_path, force=False):
49     assert len(alias_path) > 0, "Bad name for alias"
50     if os.path.exists(alias_path) and not force:
51         raise src.SatException(_("Cannot create the alias '%s'\n") % alias_path)
52     else: # find relative path
53         os.symlink(appli_path, alias_path)
54
55 ##
56 # add the definition of a module to out stream.
57 def add_module_to_appli(out, module, has_gui, module_path, logger, flagline):
58     if not os.path.exists(module_path):
59         if not flagline:
60             logger.write("\n", 3, False)
61             flagline = True
62         logger.write("  " + src.printcolors.printcWarning(_(
63                         "WARNING: module %s not installed") % module) + "\n", 3)
64
65     out.write('   <module name="%s" gui="%s" path="%s"/>\n' % (module,
66                                                                has_gui,
67                                                                module_path))
68     return flagline
69
70 ##
71 # Creates the config file to create an application with the list of modules.
72 def create_config_file(config, modules, env_file, logger):
73
74     samples = ""
75     if 'SAMPLES' in config.APPLICATION.products:
76         samples = src.product.get_product_config(config, 'SAMPLES').source_dir
77
78     config_file = src.get_tmp_filename(config, "appli_config.xml")
79     f = open(config_file, "w")
80
81     f.write('<application>\n')
82     f.write('<prerequisites path="%s"/>\n' % env_file)
83     f.write('<resources path="CatalogResources.xml"/>\n')
84     f.write('<modules>\n')
85
86     flagline = False
87     for m in modules:
88         mm = src.product.get_product_config(config, m)
89         if src.product.product_is_smesh_plugin(mm):
90             continue
91
92         if 'install_dir' in mm and bool(mm.install_dir) :
93             if src.product.product_is_cpp(mm):
94                 # cpp module
95                 for aa in src.product.get_product_components(mm):
96                     install_dir = os.path.join(config.APPLICATION.workdir,
97                                                "INSTALL")
98                     mp = os.path.join(install_dir, aa)
99                     flagline = add_module_to_appli(f,
100                                                    aa,
101                                                    "yes",
102                                                    mp,
103                                                    logger,
104                                                    flagline)
105             else:
106                 # regular module
107                 mp = mm.install_dir
108                 gui = src.get_cfg_param(mm, "has_gui", "yes")
109                 flagline = add_module_to_appli(f, m, gui, mp, logger, flagline)
110
111     f.write('</modules>\n')
112     f.write('<samples path="%s"/>\n' % samples)
113     f.write('</application>\n')
114     f.close()
115
116     return config_file
117
118 ##
119 # Customizes the application by editing SalomeApp.xml.
120 def customize_app(config, appli_dir, logger):
121     if 'configure' not in config.APPLI \
122         or len(config.APPLI.configure) == 0:
123         return
124
125     # shortcut to get an element (section or parameter) from parent.
126     def get_element(parent, name, strtype):
127         for c in parent.getchildren():
128             if c.attrib['name'] == name:
129                 return c
130
131         # element not found create it
132         elt = add_simple_node(parent, strtype)
133         elt.attrib['name'] = name
134         return elt
135
136     # shortcut method to create a node
137     def add_simple_node(parent, node_name, text=None):
138         n = etree.Element(node_name)
139         if text is not None:
140             try:
141                 n.text = text.strip("\n\t").decode("UTF-8")
142             except:
143                 sys.stderr.write("################ %s %s\n" % (node_name, text))
144                 n.text = "?"
145         parent.append(n)
146         return n
147
148     # read the app file
149     app_file = os.path.join(appli_dir, "SalomeApp.xml")
150     tree = etree.parse(app_file)
151     document = tree.getroot()
152     assert document is not None, "document tag not found"
153
154     logger.write("\n", 4)
155     for section_name in config.APPLI.configure:
156         for parameter_name in config.APPLI.configure[section_name]:
157             parameter_value = config.APPLI.configure[section_name][parameter_name]
158             logger.write("  configure: %s/%s = %s\n" % (section_name,
159                                                         parameter_name,
160                                                         parameter_value), 4)
161             section = get_element(document, section_name, "section")
162             parameter = get_element(section, parameter_name, "parameter")
163             parameter.attrib['value'] = parameter_value
164
165     # write the file
166     f = open(app_file, "w")
167     f.write("<?xml version='1.0' encoding='utf-8'?>\n")
168     f.write(etree.tostring(document, encoding='utf-8'))
169     f.close()
170
171 ##
172 # Generates the application with the config_file.
173 def generate_application(config, appli_dir, config_file, logger):
174     target_dir = os.path.dirname(appli_dir)
175
176     install_KERNEL_dir = src.product.get_product_config(config,
177                                                         'KERNEL').install_dir
178     script = os.path.join(install_KERNEL_dir, "bin", "salome", "appli_gen.py")
179     if not os.path.exists(script):
180         raise src.SatException(_("KERNEL is not installed"))
181     
182     # Add SALOME python in the environment in order to avoid python version 
183     # problems at appli_gen.py call
184     if 'Python' in config.APPLICATION.products:
185         envi = src.environment.SalomeEnviron(config,
186                                              src.environment.Environ(
187                                                               dict(os.environ)),
188                                              True)
189         envi.set_a_product('Python', logger)
190     
191     command = "python %s --prefix=%s --config=%s" % (script,
192                                                      appli_dir,
193                                                      config_file)
194     logger.write("\n>" + command + "\n", 5, False)
195     res = subprocess.call(command,
196                     shell=True,
197                     cwd=target_dir,
198                     env=envi.environ.environ,
199                     stdout=logger.logTxtFile,
200                     stderr=subprocess.STDOUT)
201     
202     if res != 0:
203         raise src.SatException(_("Cannot create application, code = %d\n") % res)
204
205     return res
206
207 ##
208 #
209 def write_step(logger, message, level=3, pad=50):
210     logger.write("%s %s " % (message, '.' * (pad - len(message.decode("UTF-8")))), level)
211     logger.flush()
212
213 ##
214 # Creates a SALOME application.
215 def create_application(config, appli_dir, catalog, logger, display=True):
216       
217     SALOME_modules = get_SALOME_modules(config)
218     
219     warn = ['KERNEL', 'GUI']
220     if display:
221         for w in warn:
222             if w not in SALOME_modules:
223                 msg = _("WARNING: module %s is required to create application\n") % w
224                 logger.write(src.printcolors.printcWarning(msg), 2)
225
226     # generate the launch file
227     retcode = generate_launch_file(config,
228                                    appli_dir,
229                                    catalog,
230                                    logger,
231                                    SALOME_modules)
232     
233     if retcode == 0:
234         cmd = src.printcolors.printcLabel("%s/runAppli" % appli_dir)
235
236     if display:
237         logger.write("\n", 3, False)
238         logger.write(_("To launch the application, type:\n"), 3, False)
239         logger.write("  %s" % (cmd), 3, False)
240         logger.write("\n", 3, False)
241     return retcode
242
243 def get_SALOME_modules(config):
244     l_modules = []
245     for product in config.APPLICATION.products:
246         product_info = src.product.get_product_config(config, product)
247         if src.product.product_is_SALOME(product_info):
248             l_modules.append(product)
249         if src.product.product_is_generated(product_info):
250             if "component_name" in product_info:
251                 l_modules.append(product_info.component_name)
252     return l_modules
253
254 ##
255 # Obsolescent way of creating the application.
256 # This method will use appli_gen to create the application directory.
257 def generate_launch_file(config, appli_dir, catalog, logger, l_SALOME_modules):
258     retcode = -1
259
260     if len(catalog) > 0 and not os.path.exists(catalog):
261         raise IOError(_("Catalog not found: %s") % catalog)
262     
263     write_step(logger, _("Creating environment files"))
264     status = src.KO_STATUS
265     try:
266         import environ
267         # generate only shells the user wants (by default bash, csh, batch)
268         # the environ command will only generate file compatible 
269         # with the current system.
270         environ.write_all_source_files(config,
271                                        logger,
272                                        silent=True)
273         status = src.OK_STATUS
274     finally:
275         logger.write(src.printcolors.printc(status) + "\n", 2, False)
276
277     # build the application
278     env_file = os.path.join(config.APPLICATION.workdir, "env_launch.sh")
279     write_step(logger, _("Building application"), level=2)
280     cf = create_config_file(config, l_SALOME_modules, env_file, logger)
281
282     # create the application directory
283     os.makedirs(appli_dir)
284
285     # generate the application
286     status = src.KO_STATUS
287     try:
288         retcode = generate_application(config, appli_dir, cf, logger)
289         customize_app(config, appli_dir, logger)
290         status = src.OK_STATUS
291     finally:
292         logger.write(src.printcolors.printc(status) + "\n", 2, False)
293
294     # copy the catalog if one
295     if len(catalog) > 0:
296         shutil.copy(catalog, os.path.join(appli_dir, "CatalogResources.xml"))
297
298     return retcode
299
300
301 ##
302 # Generates a launcher that sources Salome's python and calls original launcher
303 def generate_sourcing_launcher(config, appli_dir, logger) :
304
305     # Rename original launcher
306     launcher_name = os.path.join( appli_dir,
307                                   "bin",
308                                   "salome",
309                                   config.APPLI.launch_alias_name )
310     original_launcher = launcher_name + "-original"
311     os.rename( launcher_name, original_launcher )
312     
313     # Open new launcher
314     f = open(launcher_name, "w")
315     
316     # Write the set up of the environment
317     env = src.environment.SalomeEnviron( config,
318                                          src.fileEnviron.get_file_environ(
319                                                                         f,
320                                                                         "bash",
321                                                                         {},
322                                                                         config))
323     env.set_a_product( "Python", logger)
324     
325     # Write the call to the original launcher
326     f.write( "\n\n")
327     f.write( "# This is the call to the original launcher\n")
328     f.write( original_launcher + " $*" )
329     f.write( "\n\n")
330     
331     # Write the cleaning of the environment
332     env.finish(True)
333     
334     # Close new launcher
335     f.close()
336     os.chmod(launcher_name, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777
337
338    
339
340 ##
341 # Generates the catalog from a list of machines.
342 def generate_catalog(machines, config, logger):
343     # remove empty machines
344     machines = map(lambda l: l.strip(), machines)
345     machines = filter(lambda l: len(l) > 0, machines)
346
347     src.printcolors.print_value(logger,
348                                 _("Generate Resources Catalog"),
349                                 ", ".join(machines),
350                                 4)
351     cmd = '"cat /proc/cpuinfo | grep MHz ; cat /proc/meminfo | grep MemTotal"'
352     user = getpass.getuser()
353
354     catfile = src.get_tmp_filename(config, "CatalogResources.xml")
355     catalog = file(catfile, "w")
356     catalog.write("<!DOCTYPE ResourcesCatalog>\n<resources>\n")
357     for k in machines:
358         logger.write("    ssh %s " % (k + " ").ljust(20, '.'), 4)
359         logger.flush()
360
361         ssh_cmd = 'ssh -o "StrictHostKeyChecking no" %s %s' % (k, cmd)
362         p = subprocess.Popen(ssh_cmd, shell=True,
363                 stdin=subprocess.PIPE,
364                 stdout=subprocess.PIPE,
365                 stderr=subprocess.PIPE)
366         p.wait()
367
368         if p.returncode != 0:
369             logger.write(src.printcolors.printc(src.KO_STATUS) + "\n", 4)
370             logger.write("    " + src.printcolors.printcWarning(p.stderr.read()),
371                          2)
372         else:
373             logger.write(src.printcolors.printc(src.OK_STATUS) + "\n", 4)
374             lines = p.stdout.readlines()
375             freq = lines[0][:-1].split(':')[-1].split('.')[0].strip()
376             nb_proc = len(lines) -1
377             memory = lines[-1].split(':')[-1].split()[0].strip()
378             memory = int(memory) / 1000
379
380             catalog.write("    <machine\n")
381             catalog.write("        protocol=\"ssh\"\n")
382             catalog.write("        nbOfNodes=\"1\"\n")
383             catalog.write("        mode=\"interactif\"\n")
384             catalog.write("        OS=\"LINUX\"\n")
385             catalog.write("        CPUFreqMHz=\"%s\"\n" % freq)
386             catalog.write("        nbOfProcPerNode=\"%s\"\n" % nb_proc)
387             catalog.write("        memInMB=\"%s\"\n" % memory)
388             catalog.write("        userName=\"%s\"\n" % user)
389             catalog.write("        name=\"%s\"\n" % k)
390             catalog.write("        hostname=\"%s\"\n" % k)
391             catalog.write("    >\n")
392             catalog.write("    </machine>\n")
393
394     catalog.write("</resources>\n")
395     catalog.close()
396     return catfile
397
398 ##################################################
399
400 ##
401 # Describes the command
402 def description():
403     '''method that is called when salomeTools is called with --help option.
404     
405     :return: The text to display for the application command description.
406     :rtype: str
407     '''
408     return _("The application command creates a SALOME application.\n"
409              "WARNING: it works only for SALOME 6. Use the \"launcher\" "
410              "command for newer versions of SALOME\n\nexample:\nsat application"
411              " SALOME-6.6.0")
412
413 ##
414 # Runs the command.
415 def run(args, runner, logger):
416     '''method that is called when salomeTools is called with application
417        parameter.
418     '''
419     
420     (options, args) = parser.parse_args(args)
421
422     # check for product
423     src.check_config_has_application( runner.cfg )
424
425     application = src.printcolors.printcLabel(runner.cfg.VARS.application)
426     logger.write(_("Building application for %s\n") % application, 1)
427
428     # if section APPLI does not exists create one
429     if "APPLI" not in runner.cfg:
430         msg = _("The section APPLI is not defined in the product.")
431         logger.write(src.printcolors.printcError(msg), 1)
432         logger.write("\n", 1)
433         return 1
434
435     # get application dir
436     target_dir = runner.cfg.APPLICATION.workdir
437     if options.target:
438         target_dir = options.target
439
440     # set list of modules
441     if options.modules:
442         runner.cfg.APPLI['modules'] = options.modules
443
444     # set name and application_name
445     if options.name:
446         runner.cfg.APPLI['name'] = options.name
447         runner.cfg.APPLI['application_name'] = options.name + "_appdir"
448     
449     application_name = src.get_cfg_param(runner.cfg.APPLI,
450                                          "application_name",
451                                          runner.cfg.APPLI.name + "_appdir")
452     appli_dir = os.path.join(target_dir, application_name)
453
454     src.printcolors.print_value(logger,
455                                 _("Application directory"),
456                                 appli_dir,
457                                 3)
458
459     # get catalog
460     catalog, catalog_src = "", ""
461     if options.catalog:
462         # use catalog specified in the command line
463         catalog = options.catalog
464     elif options.gencat:
465         # generate catalog for given list of computers
466         catalog_src = options.gencat
467         catalog = generate_catalog(options.gencat.split(","),
468                                    runner.cfg,logger)
469     elif 'catalog' in runner.cfg.APPLI:
470         # use catalog specified in the product
471         if runner.cfg.APPLI.catalog.endswith(".xml"):
472             # catalog as a file
473             catalog = runner.cfg.APPLI.catalog
474         else:
475             # catalog as a list of computers
476             catalog_src = runner.cfg.APPLI.catalog
477             mlist = filter(lambda l: len(l.strip()) > 0,
478                            runner.cfg.APPLI.catalog.split(","))
479             if len(mlist) > 0:
480                 catalog = generate_catalog(runner.cfg.APPLI.catalog.split(","),
481                                            runner.cfg, logger)
482
483     # display which catalog is used
484     if len(catalog) > 0:
485         catalog = os.path.realpath(catalog)
486         if len(catalog_src) > 0:
487             src.printcolors.print_value(logger,
488                                         _("Resources Catalog"),
489                                         catalog_src,
490                                         3)
491         else:
492             src.printcolors.print_value(logger,
493                                         _("Resources Catalog"),
494                                         catalog,
495                                         3)
496
497     logger.write("\n", 3, False)
498
499     details = []
500
501     # remove previous application
502     if os.path.exists(appli_dir):
503         write_step(logger, _("Removing previous application directory"))
504         rres = src.KO_STATUS
505         try:
506             shutil.rmtree(appli_dir)
507             rres = src.OK_STATUS
508         finally:
509             logger.write(src.printcolors.printc(rres) + "\n", 3, False)
510
511     # generate the application
512     try:
513         try: # try/except/finally not supported in all version of python
514             retcode = create_application(runner.cfg, appli_dir, catalog, logger)
515         except Exception as exc:
516             details.append(str(exc))
517             raise
518     finally:
519         logger.write("\n", 3, False)
520
521     return retcode
522