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