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