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