]> SALOME platform Git repositories - tools/sat.git/blob - commands/application.py
Salome HOME
Add the application command (not fonctional yet)
[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     _('The name of the application (default is APPLI.name or runAppli)'))
32 parser.add_option('c', 'catalog', 'string', 'catalog',
33     _('The resources catalog to use'))
34 parser.add_option('t', 'target', 'string', 'target',
35     _('The directory where to create the application (default is PRODUCT.out_dir)'))
36 parser.add_option('', 'gencat', 'string', 'gencat',
37     _("""Create a resources catalog for the specified machines (separated with ',')
38 \tNOTICE: this command will ssh to retrieve information to each machine in the list"""))
39 parser.add_option('m', 'module', 'list2', 'modules',
40     _("module(s) to include in the application"))
41
42 ##
43 # Creates an alias for runAppli.
44 def make_alias(appli_path, alias_path, force=False):
45     assert len(alias_path) > 0, "Bad name for alias"
46     if os.path.exists(alias_path) and not force:
47         raise src.SatException(_("Cannot create the alias '%s'\n") % alias_path)
48     else: # find relative path
49         os.symlink(appli_path, alias_path)
50
51 ##
52 # add the definition of a module to out stream.
53 def add_module_to_appli(out, module, has_gui, module_path, logger, flagline):
54     if not os.path.exists(module_path):
55         if not flagline:
56             logger.write("\n", 3, False)
57             flagline = True
58         logger.write("  " + src.printcolors.printcWarning(_(
59                         "WARNING: module %s not installed") % module) + "\n", 3)
60
61     out.write('   <module name="%s" gui="%s" path="%s"/>\n' % (module,
62                                                                has_gui,
63                                                                module_path))
64     return flagline
65
66 ##
67 # Creates the config file to create an application with the list of modules.
68 def create_config_file(config, modules, env_file, logger):
69     if len(modules) == 0:
70         modules = config.APPLICATION.products
71
72     samples = ""
73     if 'SAMPLES' in config.APPLICATION.products:
74         samples = src.product.get_product_config(config, 'SAMPLES').source_dir
75
76     config_file = src.get_tmp_filename(config, "appli_config.xml")
77     f = open(config_file, "w")
78
79     f.write('<application>\n')
80     f.write('<prerequisites path="%s"/>\n' % env_file)
81     f.write('<resources path="CatalogResources.xml"/>\n')
82     f.write('<modules>\n')
83
84     flagline = False
85     for m in modules:
86         mm = src.product.get_product_config(config, m)
87         if src.product.module_is_smesh_plugin(mm):
88             continue
89
90         if 'install_dir' in mm and bool(mm.install_dir) :
91             if src.product.module_is_cpp(mm):
92                 # cpp module
93                 for aa in src.product.get_product_components(mm):
94                     install_dir = os.path.join(config.APPLICATION.workdir,
95                                                "INSTALL")
96                     mp = os.path.join(install_dir, aa)
97                     flagline = add_module_to_appli(f,
98                                                    aa,
99                                                    "yes",
100                                                    mp,
101                                                    logger,
102                                                    flagline)
103             else:
104                 # regular module
105                 mp = mm.install_dir
106                 gui = src.get_cfg_param(mm, "has_gui", "yes")
107                 flagline = add_module_to_appli(f, m, gui, mp, logger, flagline)
108
109     f.write('</modules>\n')
110     f.write('<samples path="%s"/>\n' % samples)
111     f.write('</application>\n')
112     f.close()
113
114     return config_file
115
116 ##
117 # Customizes the application by editing SalomeApp.xml.
118 def customize_app(config, appli_dir, logger):
119     if 'configure' not in config.APPLI \
120         or len(config.APPLI.configure) == 0:
121         return
122
123     # shortcut to get an element (section or parameter) from parent.
124     def get_element(parent, name, strtype):
125         for c in parent.getchildren():
126             if c.attrib['name'] == name:
127                 return c
128
129         # element not found create it
130         elt = add_simple_node(parent, strtype)
131         elt.attrib['name'] = name
132         return elt
133
134     # shortcut method to create a node
135     def add_simple_node(parent, node_name, text=None):
136         n = etree.Element(node_name)
137         if text is not None:
138             try:
139                 n.text = text.strip("\n\t").decode("UTF-8")
140             except:
141                 sys.stderr.write("################ %s %s\n" % (node_name, text))
142                 n.text = "?"
143         parent.append(n)
144         return n
145
146     # read the app file
147     app_file = os.path.join(appli_dir, "SalomeApp.xml")
148     tree = etree.parse(app_file)
149     document = tree.getroot()
150     assert document is not None, "document tag not found"
151
152     logger.write("\n", 4)
153     for section_name in config.APPLI.configure:
154         for parameter_name in config.APPLI.configure[section_name]:
155             parameter_value = config.APPLI.configure[section_name][parameter_name]
156             logger.write("  configure: %s/%s = %s\n" % (section_name,
157                                                         parameter_name,
158                                                         parameter_value), 4)
159             section = get_element(document, section_name, "section")
160             parameter = get_element(section, parameter_name, "parameter")
161             parameter.attrib['value'] = parameter_value
162
163     # write the file
164     f = open(app_file, "w")
165     f.write("<?xml version='1.0' encoding='utf-8'?>\n")
166     f.write(etree.tostring(document, encoding='utf-8'))
167     f.close()
168
169 ##
170 # Generates the application with the config_file.
171 def generate_application(config, appli_dir, config_file, logger):
172     target_dir = os.path.dirname(appli_dir)
173
174     install_KERNEL_dir = src.product.get_product_config(config,
175                                                         'KERNEL').install_dir
176     script = os.path.join(install_KERNEL_dir, "bin", "salome", "appli_gen.py")
177     if not os.path.exists(script):
178         raise src.SatException(_("KERNEL is not installed"))
179     
180     # Add SALOME python in the environment in order to avoid python version 
181     # problems at appli_gen.py call
182     if 'Python' in config.APPLICATIONS.products:
183         envi = src.environment.SalomeEnviron(config,
184                                              src.environment.Environ(
185                                                               dict(os.environ)),
186                                              True)
187         envi.set_a_product('Python', logger)
188     
189     command = "python %s --prefix=%s --config=%s" % (script,
190                                                      appli_dir,
191                                                      config_file)
192     logger.write("\n>" + command + "\n", 5, False)
193     res = subprocess.call(command,
194                     shell=True,
195                     cwd=target_dir,
196                     env=envi.environ.environ,
197                     stdout=logger.log_file,
198                     stderr=subprocess.STDOUT)
199     
200     if res != 0:
201         raise src.SatException(_("Cannot create application, code = %d\n") % res)
202
203     return res
204
205 ##
206 #
207 def write_step(logger, message, level=3, pad=50):
208     logger.write("%s %s " % (message, '.' * (pad - len(message.decode("UTF-8")))), level)
209     logger.flush()
210
211 ##
212 # Creates a SALOME application.
213 def create_application(config, appli_dir, catalog, logger, display=True):
214     
215     # check modules to add to installation
216     modules = []
217     if 'modules' in config.APPLI:
218         modules = config.APPLI.modules
219         # add prerequisites for the module and its dependencies
220         products = {}
221         for module in modules:
222             prelist = config.TOOLS.common.module_info[module].pre_depend
223             for prereq in prelist:
224                 # add prerequisites define in module_info AND product.prerequis
225                 if prereq in config.PRODUCT.prerequis and not prerequis.has_key(prereq):
226                     prerequis[prereq] = config.PRODUCT.prerequis[prereq]
227     
228     if len(modules) == 0:
229         modules = src.get_cfg_param(config.PRODUCT, "all_modules", config.PRODUCT.modules)
230         prerequis = config.PRODUCT.prerequis
231
232     env_info = { 'modules': modules, 'prerequis': prerequis }
233
234     cmd_old, cmd_new = "", ""
235
236     warn = ['KERNEL', 'GUI']
237     if display:
238         for w in warn:
239             if w not in env_info['modules']:
240                 msg = _("WARNING: module %s is required to create application\n") % w
241                 logger.write(src.printcolors.printcWarning(msg), 2)
242
243     # old way for application
244     retcode = generate_launch_file_old(config, appli_dir, catalog, logger, env_info=env_info)
245     if retcode == 0:
246         cmd_old = src.printcolors.printcLabel("%s/runAppli" % appli_dir)
247
248     # new way for application
249     VersionSalome = src.get_salome_version(config)
250            
251     if VersionSalome >= 751 :
252         filename = generate_launch_file(config, appli_dir, catalog, logger, env_info)
253         cmd_new = src.printcolors.printcLabel(filename)
254
255     if display:
256         logger.write("\n", 3, False)
257         logger.write(_("To launch the application, type:\n"), 3, False)
258         if len(cmd_old) > 0:
259             logger.write("  %s" % (cmd_old), 3, False)
260             logger.write("\n", 3, False)
261         if len(cmd_new) > 0:
262             logger.write("  %s (%s)" % (cmd_new, _("new command")), 3, False)
263             logger.write("\n", 3, False)    
264     return retcode
265
266 ##
267 # Obsolescent way of creating the application.
268 # This method will use appli_gen to create the application directory.
269 def generate_launch_file_old(config, appli_dir, catalog, logger, env_info=None):
270     retcode = -1
271
272     if len(catalog) > 0 and not os.path.exists(catalog):
273         raise IOError(_("Catalog not found: %s") % catalog)
274     
275     write_step(logger, _("Creating environment files"))
276     status = src.KO_STATUS
277     try:
278         import environ
279         # generate only shells the user wants (by default bash, csh, batch)
280         # the environ command will only generate file compatible with the current system.
281         shells = src.get_cfg_param(config.TOOLS.environ,
282                                    "shells",
283                                    [ "bash", "batch"])
284         environ.write_all_source_files(config,
285                                        logger,
286                                        silent=True,
287                                        shells=shells)
288         status = src.OK_STATUS
289     finally:
290         logger.write(src.printcolors.printc(status) + "\n", 2, False)
291
292     # build the application
293     env_file = os.path.join(config.PRODUCT.out_dir, "env_launch.sh")
294     write_step(logger, _("Building application"), level=2)
295     cf = create_config_file(config, env_info['modules'], env_file, logger)
296
297     # create the application directory
298     os.makedirs(appli_dir)
299
300     # generate the application
301     status = src.KO_STATUS
302     try:
303         retcode = generate_application(config, appli_dir, cf, logger)
304         customize_app(config, appli_dir, logger)
305         status = src.OK_STATUS
306     finally:
307         logger.write(src.printcolors.printc(status) + "\n", 2, False)
308
309     # copy the catalog if one
310     if len(catalog) > 0:
311         shutil.copy(catalog, os.path.join(appli_dir, "CatalogResources.xml"))
312
313     return retcode
314
315
316 ##
317 # Generates a launcher that sources Salome's python and calls original launcher
318 def generate_sourcing_launcher(config, appli_dir, logger) :
319
320     # Rename original launcher
321     launcher_name = os.path.join( appli_dir,
322                                   "bin",
323                                   "salome",
324                                   config.APPLI.launch_alias_name )
325     original_launcher = launcher_name + "-original"
326     os.rename( launcher_name, original_launcher )
327     
328     # Open new launcher
329     f = open(launcher_name, "w")
330     
331     # Write the set up of the environment
332     env = src.environment.SalomeEnviron( config,
333                                          src.fileEnviron.get_file_environ(
334                                                                         f,
335                                                                         "bash",
336                                                                         {},
337                                                                         config))
338     env.set_a_product( "Python", logger)
339     
340     # Write the call to the original launcher
341     f.write( "\n\n")
342     f.write( "# This is the call to the original launcher\n")
343     f.write( original_launcher + " $*" )
344     f.write( "\n\n")
345     
346     # Write the cleaning of the environment
347     env.finish(True)
348     
349     # Close new launcher
350     f.close()
351     os.chmod(launcher_name, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777
352
353 ##
354 # New method to create an application
355 def generate_launch_file(config,
356                          appli_dir,
357                          catalog,
358                          logger,
359                          env_info=None):
360     
361     out_dir = config.PRODUCT.out_dir
362
363     basefilename = config.APPLI.name
364     if "launch_alias_name" in config.APPLI :
365         basefilename = config.APPLI.launch_alias_name
366     filename = os.path.join(appli_dir, "bin/salome", basefilename)
367     if os.path.exists(filename): os.remove(filename)
368     before, after = src.fileEnviron.withProfile.split(
369                                    "# here your local standalone environment\n")
370
371     # create an environment file writer
372     writer = src.environment.FileEnvWriter(config,
373                                            logger,
374                                            out_dir,
375                                            src_root=None,
376                                            single_dir=False,
377                                            env_info=env_info)
378     #writer.silent = False
379
380     # create the command file
381     launch_file = open(filename, "w")
382     launch_file.write(before)
383     writer.write_cfgForPy_file(launch_file)
384     launch_file.write(after)
385     launch_file.close()
386     os.chmod(filename, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777
387       
388     # If native python < 2.6, write a launcher that sources Salome's 
389     # python before calling the original launcher
390     if config.VARS.python < "2.6" :
391         generate_sourcing_launcher(config, appli_dir, logger)
392
393     return filename
394     
395
396 ##
397 # Generates the catalog from a list of machines.
398 def generate_catalog(machines, config, logger):
399     # remove empty machines
400     machines = map(lambda l: l.strip(), machines)
401     machines = filter(lambda l: len(l) > 0, machines)
402
403     src.printcolors.print_value(logger,
404                                 _("Generate Resources Catalog"),
405                                 ", ".join(machines),
406                                 4)
407     cmd = '"cat /proc/cpuinfo | grep MHz ; cat /proc/meminfo | grep MemTotal"'
408     user = getpass.getuser()
409
410     catfile = src.get_tmp_filename(config, "CatalogResources.xml")
411     catalog = file(catfile, "w")
412     catalog.write("<!DOCTYPE ResourcesCatalog>\n<resources>\n")
413     for k in machines:
414         logger.write("    ssh %s " % (k + " ").ljust(20, '.'), 4)
415         logger.flush()
416
417         ssh_cmd = 'ssh -o "StrictHostKeyChecking no" %s %s' % (k, cmd)
418         p = subprocess.Popen(ssh_cmd, shell=True,
419                 stdin=subprocess.PIPE,
420                 stdout=subprocess.PIPE,
421                 stderr=subprocess.PIPE)
422         p.wait()
423
424         if p.returncode != 0:
425             logger.write(src.printcolors.printc(src.KO_STATUS) + "\n", 4)
426             logger.write("    " + src.printcolors.printcWarning(p.stderr.read()),
427                          2)
428         else:
429             logger.write(src.printcolors.printc(src.OK_STATUS) + "\n", 4)
430             lines = p.stdout.readlines()
431             freq = lines[0][:-1].split(':')[-1].split('.')[0].strip()
432             nb_proc = len(lines) -1
433             memory = lines[-1].split(':')[-1].split()[0].strip()
434             memory = int(memory) / 1000
435
436             catalog.write("    <machine\n")
437             catalog.write("        protocol=\"ssh\"\n")
438             catalog.write("        nbOfNodes=\"1\"\n")
439             catalog.write("        mode=\"interactif\"\n")
440             catalog.write("        OS=\"LINUX\"\n")
441             catalog.write("        CPUFreqMHz=\"%s\"\n" % freq)
442             catalog.write("        nbOfProcPerNode=\"%s\"\n" % nb_proc)
443             catalog.write("        memInMB=\"%s\"\n" % memory)
444             catalog.write("        userName=\"%s\"\n" % user)
445             catalog.write("        name=\"%s\"\n" % k)
446             catalog.write("        hostname=\"%s\"\n" % k)
447             catalog.write("    >\n")
448             catalog.write("    </machine>\n")
449
450     catalog.write("</resources>\n")
451     catalog.close()
452     return catfile
453
454 ##################################################
455
456 ##
457 # Describes the command
458 def description():
459     '''method that is called when salomeTools is called with --help option.
460     
461     :return: The text to display for the application command description.
462     :rtype: str
463     '''
464     return _("""The application command creates a SALOME application.\n"""
465              """WARNING: it works only for SALOME 6. Use the "launcher" """
466              """command for newer versions of SALOME""")
467
468 ##
469 # Runs the command.
470 def run(args, runner, logger):
471     '''method that is called when salomeTools is called with application
472        parameter.
473     '''
474     
475     (options, args) = parser.parse_args(args)
476
477     # check for product
478     src.check_config_has_application( runner.cfg )
479
480     application = src.printcolors.printcLabel(runner.cfg.VARS.application)
481     logger.write(_("Building application for %s\n") % application, 1)
482
483     # if section APPLI does not exists create one
484     if "APPLI" not in runner.cfg:
485         msg = _("The section APPLI is not defined in the product.")
486         logger.write(src.printcolors.printcError(msg), 1)
487         return 1
488
489     # get application dir
490     target_dir = runner.cfg.APPLICATION.workdir
491     if options.target:
492         target_dir = options.target
493
494     # set list of modules
495     if options.modules:
496         runner.cfg.APPLI['modules'] = options.modules
497
498     # set name and application_name
499     if options.name:
500         runner.cfg.APPLI['name'] = options.name
501         runner.cfg.APPLI['application_name'] = options.name + "_appdir"
502     
503     application_name = src.get_cfg_param(runner.cfg.APPLI,
504                                          "application_name",
505                                          runner.cfg.APPLI.name + "_appdir")
506     appli_dir = os.path.join(target_dir, application_name)
507
508     src.printcolors.print_value(logger,
509                                 _("Application directory"),
510                                 appli_dir,
511                                 3)
512
513     # get catalog
514     catalog, catalog_src = "", ""
515     if options.catalog:
516         # use catalog specified in the command line
517         catalog = options.catalog
518     elif options.gencat:
519         # generate catalog for given list of computers
520         catalog_src = options.gencat
521         catalog = generate_catalog(options.gencat.split(","),
522                                    runner.cfg,logger)
523     elif 'catalog' in runner.cfg.APPLI:
524         # use catalog specified in the product
525         if runner.cfg.APPLI.catalog.endswith(".xml"):
526             # catalog as a file
527             catalog = runner.cfg.APPLI.catalog
528         else:
529             # catalog as a list of computers
530             catalog_src = runner.cfg.APPLI.catalog
531             mlist = filter(lambda l: len(l.strip()) > 0,
532                            runner.cfg.APPLI.catalog.split(","))
533             if len(mlist) > 0:
534                 catalog = generate_catalog(runner.cfg.APPLI.catalog.split(","),
535                                            runner.cfg, logger)
536
537     # display which catalog is used
538     if len(catalog) > 0:
539         catalog = os.path.realpath(catalog)
540         if len(catalog_src) > 0:
541             src.printcolors.print_value(logger,
542                                         _("Resources Catalog"),
543                                         catalog_src,
544                                         3)
545         else:
546             src.printcolors.print_value(logger,
547                                         _("Resources Catalog"),
548                                         catalog,
549                                         3)
550
551     logger.write("\n", 3, False)
552
553     details = []
554
555     # remove previous application
556     if os.path.exists(appli_dir):
557         write_step(logger, _("Removing previous application directory"))
558         rres = src.KO_STATUS
559         try:
560             shutil.rmtree(appli_dir)
561             rres = src.OK_STATUS
562         finally:
563             logger.write(src.printcolors.printc(rres) + "\n", 3, False)
564
565     # generate the application
566     try:
567         try: # try/except/finally not supported in all version of python
568             retcode = create_application(runner.cfg, appli_dir, catalog, logger)
569         except Exception as exc:
570             details.append(str(exc))
571             raise
572     finally:
573         logger.write("\n", 3, False)
574
575     return retcode
576