]> SALOME platform Git repositories - tools/sat.git/blob - commands/application.py
Salome HOME
Improve help of each command
[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     return l_modules
250
251 ##
252 # Obsolescent way of creating the application.
253 # This method will use appli_gen to create the application directory.
254 def generate_launch_file(config, appli_dir, catalog, logger, l_SALOME_modules):
255     retcode = -1
256
257     if len(catalog) > 0 and not os.path.exists(catalog):
258         raise IOError(_("Catalog not found: %s") % catalog)
259     
260     write_step(logger, _("Creating environment files"))
261     status = src.KO_STATUS
262     try:
263         import environ
264         # generate only shells the user wants (by default bash, csh, batch)
265         # the environ command will only generate file compatible 
266         # with the current system.
267         environ.write_all_source_files(config,
268                                        logger,
269                                        silent=True)
270         status = src.OK_STATUS
271     finally:
272         logger.write(src.printcolors.printc(status) + "\n", 2, False)
273
274     # build the application
275     env_file = os.path.join(config.APPLICATION.workdir, "env_launch.sh")
276     write_step(logger, _("Building application"), level=2)
277     cf = create_config_file(config, l_SALOME_modules, env_file, logger)
278
279     # create the application directory
280     os.makedirs(appli_dir)
281
282     # generate the application
283     status = src.KO_STATUS
284     try:
285         retcode = generate_application(config, appli_dir, cf, logger)
286         customize_app(config, appli_dir, logger)
287         status = src.OK_STATUS
288     finally:
289         logger.write(src.printcolors.printc(status) + "\n", 2, False)
290
291     # copy the catalog if one
292     if len(catalog) > 0:
293         shutil.copy(catalog, os.path.join(appli_dir, "CatalogResources.xml"))
294
295     return retcode
296
297
298 ##
299 # Generates a launcher that sources Salome's python and calls original launcher
300 def generate_sourcing_launcher(config, appli_dir, logger) :
301
302     # Rename original launcher
303     launcher_name = os.path.join( appli_dir,
304                                   "bin",
305                                   "salome",
306                                   config.APPLI.launch_alias_name )
307     original_launcher = launcher_name + "-original"
308     os.rename( launcher_name, original_launcher )
309     
310     # Open new launcher
311     f = open(launcher_name, "w")
312     
313     # Write the set up of the environment
314     env = src.environment.SalomeEnviron( config,
315                                          src.fileEnviron.get_file_environ(
316                                                                         f,
317                                                                         "bash",
318                                                                         {},
319                                                                         config))
320     env.set_a_product( "Python", logger)
321     
322     # Write the call to the original launcher
323     f.write( "\n\n")
324     f.write( "# This is the call to the original launcher\n")
325     f.write( original_launcher + " $*" )
326     f.write( "\n\n")
327     
328     # Write the cleaning of the environment
329     env.finish(True)
330     
331     # Close new launcher
332     f.close()
333     os.chmod(launcher_name, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777
334
335    
336
337 ##
338 # Generates the catalog from a list of machines.
339 def generate_catalog(machines, config, logger):
340     # remove empty machines
341     machines = map(lambda l: l.strip(), machines)
342     machines = filter(lambda l: len(l) > 0, machines)
343
344     src.printcolors.print_value(logger,
345                                 _("Generate Resources Catalog"),
346                                 ", ".join(machines),
347                                 4)
348     cmd = '"cat /proc/cpuinfo | grep MHz ; cat /proc/meminfo | grep MemTotal"'
349     user = getpass.getuser()
350
351     catfile = src.get_tmp_filename(config, "CatalogResources.xml")
352     catalog = file(catfile, "w")
353     catalog.write("<!DOCTYPE ResourcesCatalog>\n<resources>\n")
354     for k in machines:
355         logger.write("    ssh %s " % (k + " ").ljust(20, '.'), 4)
356         logger.flush()
357
358         ssh_cmd = 'ssh -o "StrictHostKeyChecking no" %s %s' % (k, cmd)
359         p = subprocess.Popen(ssh_cmd, shell=True,
360                 stdin=subprocess.PIPE,
361                 stdout=subprocess.PIPE,
362                 stderr=subprocess.PIPE)
363         p.wait()
364
365         if p.returncode != 0:
366             logger.write(src.printcolors.printc(src.KO_STATUS) + "\n", 4)
367             logger.write("    " + src.printcolors.printcWarning(p.stderr.read()),
368                          2)
369         else:
370             logger.write(src.printcolors.printc(src.OK_STATUS) + "\n", 4)
371             lines = p.stdout.readlines()
372             freq = lines[0][:-1].split(':')[-1].split('.')[0].strip()
373             nb_proc = len(lines) -1
374             memory = lines[-1].split(':')[-1].split()[0].strip()
375             memory = int(memory) / 1000
376
377             catalog.write("    <machine\n")
378             catalog.write("        protocol=\"ssh\"\n")
379             catalog.write("        nbOfNodes=\"1\"\n")
380             catalog.write("        mode=\"interactif\"\n")
381             catalog.write("        OS=\"LINUX\"\n")
382             catalog.write("        CPUFreqMHz=\"%s\"\n" % freq)
383             catalog.write("        nbOfProcPerNode=\"%s\"\n" % nb_proc)
384             catalog.write("        memInMB=\"%s\"\n" % memory)
385             catalog.write("        userName=\"%s\"\n" % user)
386             catalog.write("        name=\"%s\"\n" % k)
387             catalog.write("        hostname=\"%s\"\n" % k)
388             catalog.write("    >\n")
389             catalog.write("    </machine>\n")
390
391     catalog.write("</resources>\n")
392     catalog.close()
393     return catfile
394
395 ##################################################
396
397 ##
398 # Describes the command
399 def description():
400     '''method that is called when salomeTools is called with --help option.
401     
402     :return: The text to display for the application command description.
403     :rtype: str
404     '''
405     return _("The application command creates a SALOME application.\n"
406              "WARNING: it works only for SALOME 6. Use the \"launcher\" "
407              "command for newer versions of SALOME\n\nexample:\nsat application"
408              " SALOME-6.6.0")
409
410 ##
411 # Runs the command.
412 def run(args, runner, logger):
413     '''method that is called when salomeTools is called with application
414        parameter.
415     '''
416     
417     (options, args) = parser.parse_args(args)
418
419     # check for product
420     src.check_config_has_application( runner.cfg )
421
422     application = src.printcolors.printcLabel(runner.cfg.VARS.application)
423     logger.write(_("Building application for %s\n") % application, 1)
424
425     # if section APPLI does not exists create one
426     if "APPLI" not in runner.cfg:
427         msg = _("The section APPLI is not defined in the product.")
428         logger.write(src.printcolors.printcError(msg), 1)
429         logger.write("\n", 1)
430         return 1
431
432     # get application dir
433     target_dir = runner.cfg.APPLICATION.workdir
434     if options.target:
435         target_dir = options.target
436
437     # set list of modules
438     if options.modules:
439         runner.cfg.APPLI['modules'] = options.modules
440
441     # set name and application_name
442     if options.name:
443         runner.cfg.APPLI['name'] = options.name
444         runner.cfg.APPLI['application_name'] = options.name + "_appdir"
445     
446     application_name = src.get_cfg_param(runner.cfg.APPLI,
447                                          "application_name",
448                                          runner.cfg.APPLI.name + "_appdir")
449     appli_dir = os.path.join(target_dir, application_name)
450
451     src.printcolors.print_value(logger,
452                                 _("Application directory"),
453                                 appli_dir,
454                                 3)
455
456     # get catalog
457     catalog, catalog_src = "", ""
458     if options.catalog:
459         # use catalog specified in the command line
460         catalog = options.catalog
461     elif options.gencat:
462         # generate catalog for given list of computers
463         catalog_src = options.gencat
464         catalog = generate_catalog(options.gencat.split(","),
465                                    runner.cfg,logger)
466     elif 'catalog' in runner.cfg.APPLI:
467         # use catalog specified in the product
468         if runner.cfg.APPLI.catalog.endswith(".xml"):
469             # catalog as a file
470             catalog = runner.cfg.APPLI.catalog
471         else:
472             # catalog as a list of computers
473             catalog_src = runner.cfg.APPLI.catalog
474             mlist = filter(lambda l: len(l.strip()) > 0,
475                            runner.cfg.APPLI.catalog.split(","))
476             if len(mlist) > 0:
477                 catalog = generate_catalog(runner.cfg.APPLI.catalog.split(","),
478                                            runner.cfg, logger)
479
480     # display which catalog is used
481     if len(catalog) > 0:
482         catalog = os.path.realpath(catalog)
483         if len(catalog_src) > 0:
484             src.printcolors.print_value(logger,
485                                         _("Resources Catalog"),
486                                         catalog_src,
487                                         3)
488         else:
489             src.printcolors.print_value(logger,
490                                         _("Resources Catalog"),
491                                         catalog,
492                                         3)
493
494     logger.write("\n", 3, False)
495
496     details = []
497
498     # remove previous application
499     if os.path.exists(appli_dir):
500         write_step(logger, _("Removing previous application directory"))
501         rres = src.KO_STATUS
502         try:
503             shutil.rmtree(appli_dir)
504             rres = src.OK_STATUS
505         finally:
506             logger.write(src.printcolors.printc(rres) + "\n", 3, False)
507
508     # generate the application
509     try:
510         try: # try/except/finally not supported in all version of python
511             retcode = create_application(runner.cfg, appli_dir, catalog, logger)
512         except Exception as exc:
513             details.append(str(exc))
514             raise
515     finally:
516         logger.write("\n", 3, False)
517
518     return retcode
519