Salome HOME
ajout option d'impression des graphes de dépendance
[tools/sat.git] / commands / launcher.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 platform
21 import shutil
22 import getpass
23 import subprocess
24 import stat
25
26 import src
27 import src.debug as DBG
28
29 parser = src.options.Options()
30
31 parser.add_option('n', 'name', 'string', 'name', _('Optional: The name of the'
32                   ' launcher (default is APPLICATION.profile.launcher_name)'))
33 parser.add_option('e', 'exe', 'string', 'path_exe', _('Use this option to generate a launcher which sets'
34                   ' the environment and call the executable given as argument'
35                   ' (its relative path to application workdir, or an exe name present in appli PATH)'))
36 parser.add_option('p', 'products', 'list2', 'products',
37     _("Optional: Includes only the specified products."))
38 parser.add_option('c', 'catalog', 'string', 'catalog',
39                   _('Optional: The resources catalog to use'))
40 parser.add_option('', 'gencat', 'string', 'gencat',
41                   _("Optional: Create a resources catalog for the specified machines "
42                   "(separated with ',') \n\tNOTICE: this command will ssh to retrieve"
43                   " information to each machine in the list"))
44 parser.add_option('', 'use_mesa', 'boolean', 'use_mesa',
45                   _("Optional: Create a launcher that will use mesa products\n\t"
46                   "It can be usefull whan salome is used on a remote machine through ssh"))
47 parser.add_option('', 'no_path_init', 'boolean', 'no_path_init',
48                  _("Optional: Create a launcher that will not reinitilise all path variables\n\t"
49                    "By default only PATH is not reinitialised (its value is inherited from "
50                    "user's environment)\n\tUse no_path_init option to suppress the reinitilisation"
51                    " of every paths (LD_LIBRARY_PATH, PYTHONPATH, ...)"))
52
53
54 def generate_launch_file(config,
55                          logger,
56                          launcher_name,
57                          pathlauncher,
58                          path_exe,
59                          env_info,
60                          display=True,
61                          additional_env={},
62                          no_path_init=False):
63     '''Generates the launcher file.
64     
65     :param config Config: The global configuration
66     :param logger Logger: The logger instance to use for the display 
67                           and logging
68     :param launcher_name str: The name of the launcher to generate
69     :param path_exe str: The executable to use (either a relative path to 
70                          application workdir, or an exe name in the path)
71     :param pathlauncher str: The path to the launcher to generate
72     :param display boolean: If False, do not print anything in the terminal
73     :param additional_env dict: The dict giving additional 
74                                 environment variables
75     :param env_info str: The list of products to add in the files.
76     :return: The launcher file path.
77     :rtype: str
78     '''
79     # build the launcher path, delete it if it exists
80     filepath = os.path.join(pathlauncher, launcher_name)
81     if os.path.exists(filepath):
82         os.remove(filepath)
83     kernel_root_dir=None
84     cmd=None
85     salome_application_name=None
86     app_root_dir=None
87
88     if path_exe:
89         #case of a launcher based upon an executable
90         
91         if os.path.basename(path_exe) != path_exe:
92             # for a relative (to workdir) path 
93             # build absolute path of exe and check it
94             exepath=os.path.join(config.APPLICATION.workdir, path_exe)
95             if not os.path.exists(exepath):
96                 raise src.SatException(_("cannot find executable given : %s" % exepath))
97         else:
98             exepath=path_exe 
99
100         # select the shell for the launcher (bast/bat)
101         # and build the command used to launch the exe
102         if src.architecture.is_windows():
103             shell="bat"
104             cmd="\n\nrem Launch exe with user arguments\n%s " % exepath + "%*"
105         else:
106             shell="bash"
107             cmd='\n\n# Launch exe with user arguments\n%s "$*"' % exepath
108
109     else:
110         #case of a salome python2/3 launcher
111         shell="cfgForPy"
112
113         # get KERNEL bin installation path 
114         # (in order for the launcher to get python salomeContext API)
115         kernel_cfg = src.product.get_product_config(config, "KERNEL")
116         if not src.product.check_installation(config, kernel_cfg):
117             raise src.SatException(_("KERNEL is not installed"))
118         kernel_root_dir = kernel_cfg.install_dir
119         # set kernel bin dir (considering fhs property)
120         if src.get_property_in_product_cfg(kernel_cfg, "fhs"):
121             bin_kernel_install_dir = os.path.join(kernel_root_dir,"bin") 
122         else:
123             bin_kernel_install_dir = os.path.join(kernel_root_dir,"bin","salome") 
124
125         # Add two sat variables used by fileEnviron to choose the right launcher header 
126         # and do substitutions
127         additional_env['sat_bin_kernel_install_dir'] = bin_kernel_install_dir
128         if "python3" in config.APPLICATION and config.APPLICATION.python3 == "yes":
129             additional_env['sat_python_version'] = 3
130         else:
131             additional_env['sat_python_version'] = 2
132
133     # check if the application contains an application module
134     l_product_info = src.product.get_products_infos(config.APPLICATION.products.keys(),
135                                                     config)
136     for prod_name, prod_info in l_product_info:
137         # look for a salome application
138         if src.get_property_in_product_cfg(prod_info, "is_salome_application") == "yes":
139             # if user choose -p option (env_info not None), the set appli name only if product was selected.
140             if env_info == None or ( prod_name in env_info):
141                 salome_application_name=prod_info.install_dir
142             continue
143
144     # if the application contains an application module, we set ABSOLUTE_APPLI_PATH to it.
145     # if not we set it to KERNEL_INSTALL_DIR, which is sufficient, except for salome test
146     if salome_application_name:
147         app_root_dir=salome_application_name
148     elif kernel_root_dir:
149         app_root_dir=kernel_root_dir
150
151     # Add the APPLI and ABSOLUTE_APPLI_PATH variable
152     additional_env['APPLI'] = filepath
153     if app_root_dir:
154         additional_env['ABSOLUTE_APPLI_PATH'] = app_root_dir
155
156     # create an environment file writer
157     writer = src.environment.FileEnvWriter(config,
158                                            logger,
159                                            pathlauncher,
160                                            None,
161                                            env_info)
162
163     # Display some information
164     if display:
165         # Write the launcher file
166         logger.write(_("Generating launcher for %s :\n") % 
167                      src.printcolors.printcLabel(config.VARS.application), 1)
168         logger.write("  %s\n" % src.printcolors.printcLabel(filepath), 1)
169     
170     # Write the launcher
171     writer.write_env_file(filepath, 
172                           False,  # for launch
173                           shell,
174                           additional_env=additional_env,
175                           no_path_init=no_path_init)
176     
177
178     # ... and append the launch of the exe 
179     if cmd:
180         with open(filepath, "a") as exe_launcher:
181             exe_launcher.write(cmd)
182
183     # change the rights in order to make the file executable for everybody
184     os.chmod(filepath,
185              stat.S_IRUSR |
186              stat.S_IRGRP |
187              stat.S_IROTH |
188              stat.S_IWUSR |
189              stat.S_IXUSR |
190              stat.S_IXGRP |
191              stat.S_IXOTH)
192     return filepath
193
194
195 def generate_catalog(machines, config, logger):
196     """Generates an xml catalog file from a list of machines.
197     
198     :param machines List: The list of machines to add in the catalog   
199     :param config Config: The global configuration
200     :param logger Logger: The logger instance to use for the display 
201                           and logging
202     :return: The catalog file path.
203     :rtype: str
204     """
205     # remove empty machines
206     machines = map(lambda l: l.strip(), machines)
207     machines = filter(lambda l: len(l) > 0, machines)
208     
209     # log something
210     src.printcolors.print_value(logger, _("Generate Resources Catalog"),
211                                 ", ".join(machines), 4)
212     
213     # The command to execute on each machine in order to get some information
214     cmd = '"cat /proc/cpuinfo | grep MHz ; cat /proc/meminfo | grep MemTotal"'
215     user = getpass.getuser()
216
217     # Create the catalog path
218     catfile = src.get_tmp_filename(config, "CatalogResources.xml")
219     with open(catfile, 'w') as catalog:
220         # Write into it
221         catalog.write("<!DOCTYPE ResourcesCatalog>\n<resources>\n")
222         for k in machines:
223             if not src.architecture.is_windows(): 
224                 logger.write("    ssh %s " % (k + " ").ljust(20, '.'), 4)
225                 logger.flush()
226
227                 # Verify that the machine is accessible
228                 ssh_cmd = 'ssh -o "StrictHostKeyChecking no" %s %s' % (k, cmd)
229                 p = subprocess.Popen(ssh_cmd, shell=True,
230                         stdin=subprocess.PIPE,
231                         stdout=subprocess.PIPE,
232                         stderr=subprocess.PIPE)
233                 p.wait()
234
235                 machine_access = (p.returncode == 0) 
236                 if not machine_access: # The machine is not accessible
237                     logger.write(src.printcolors.printc(src.KO_STATUS) + "\n", 4)
238                     logger.write("    " + 
239                                  src.printcolors.printcWarning(p.stderr.read()), 2)
240                 else:
241                     # The machine is accessible, write the corresponding section on
242                     # the xml file
243                     logger.write(src.printcolors.printc(src.OK_STATUS) + "\n", 4)
244                     lines = p.stdout.readlines()
245                     freq = lines[0][:-1].split(':')[-1].split('.')[0].strip()
246                     nb_proc = len(lines) -1
247                     memory = lines[-1].split(':')[-1].split()[0].strip()
248                     memory = int(memory) / 1000
249
250             catalog.write("    <machine\n")
251             catalog.write("        protocol=\"ssh\"\n")
252             catalog.write("        nbOfNodes=\"1\"\n")
253             catalog.write("        mode=\"interactif\"\n")
254             catalog.write("        OS=\"LINUX\"\n")
255
256             if (not src.architecture.is_windows()) and machine_access :
257                 catalog.write("        CPUFreqMHz=\"%s\"\n" % freq)
258                 catalog.write("        nbOfProcPerNode=\"%s\"\n" % nb_proc)
259                 catalog.write("        memInMB=\"%s\"\n" % memory)
260
261             catalog.write("        userName=\"%s\"\n" % user)
262             catalog.write("        name=\"%s\"\n" % k)
263             catalog.write("        hostname=\"%s\"\n" % k)
264             catalog.write("    >\n")
265             catalog.write("    </machine>\n")
266
267         catalog.write("</resources>\n")
268     return catfile
269
270 def copy_catalog(config, catalog_path):
271     """Copy the xml catalog file into the right location
272     
273     :param config Config: The global configuration
274     :param catalog_path str: the catalog file path
275     :return: The environment dictionary corresponding to the file path.
276     :rtype: Dict
277     """
278     # Verify the existence of the file
279     if not os.path.exists(catalog_path):
280         raise IOError(_("Catalog not found: %s") % catalog_path)
281     # Get the application directory and copy catalog inside
282     out_dir = config.APPLICATION.workdir
283     new_catalog_path = os.path.join(out_dir, "CatalogResources.xml")
284     # Do the copy
285     if catalog_path != new_catalog_path:
286         shutil.copy(catalog_path, new_catalog_path)
287     additional_environ = {'USER_CATALOG_RESOURCES_FILE' : new_catalog_path}
288     return additional_environ
289
290
291
292 ##################################################
293
294 ##
295 # Describes the command
296 def description():
297     return _("The launcher command generates a SALOME launcher.\n\nexample:"
298              "\nsat launcher SALOME-master")
299
300 ##
301 # Runs the command.
302 def run(args, runner, logger):
303
304     # check for product
305     (options, args) = parser.parse_args(args)
306
307     # Verify that the command was called with an application
308     src.check_config_has_application( runner.cfg )
309     
310     # Determine the launcher name (from option, profile section or by default "salome")
311     if options.products is None:
312         environ_info = None
313     else:
314         # add products specified by user (only products 
315         # included in the application)
316         environ_info = filter(lambda l:
317                               l in runner.cfg.APPLICATION.products.keys(),
318                               options.products)
319     if options.name:
320         launcher_name = options.name
321     else:
322         launcher_name = src.get_launcher_name(runner.cfg)
323
324     no_path_initialisation=False
325     if options.no_path_init:
326         no_path_initialisation = True
327         
328     # set the launcher path
329     launcher_path = runner.cfg.APPLICATION.workdir
330
331     # Copy a catalog if the option is called
332     additional_environ = {}
333     if options.catalog:
334         additional_environ = copy_catalog(runner.cfg, options.catalog)
335
336     # Generate a catalog of resources if the corresponding option was called
337     if options.gencat:
338         catalog_path  = generate_catalog(options.gencat.split(","),
339                                          runner.cfg,
340                                          logger)
341         additional_environ = copy_catalog(runner.cfg, catalog_path)
342
343     # activate mesa use in the generated launcher
344     if options.use_mesa:
345         src.activate_mesa_property(runner.cfg)
346
347     # option -e has precedence over section profile
348     if not options.path_exe and src.get_launcher_exe(runner.cfg):
349         options.path_exe=src.get_launcher_exe(runner.cfg)
350
351     # Generate the launcher
352     generate_launch_file(runner.cfg,
353                          logger,
354                          launcher_name,
355                          launcher_path,
356                          options.path_exe,
357                          additional_env = additional_environ,
358                          env_info=environ_info,
359                          no_path_init = no_path_initialisation )
360
361     return 0