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