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