Salome HOME
Merge branch 'master' of https://codev-tuleap.cea.fr/plugins/git/salome/sat
[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             salome_application_name=prod_info.install_dir
140             continue
141
142     # if the application contains an application module, we set ABSOLUTE_APPLI_PATH to it.
143     # if not we set it to KERNEL_INSTALL_DIR, which is sufficient, except for salome test
144     if salome_application_name:
145         app_root_dir=salome_application_name
146     elif kernel_root_dir:
147         app_root_dir=kernel_root_dir
148
149     # Add the APPLI and ABSOLUTE_APPLI_PATH variable
150     additional_env['APPLI'] = filepath
151     if app_root_dir:
152         additional_env['ABSOLUTE_APPLI_PATH'] = app_root_dir
153
154     # create an environment file writer
155     writer = src.environment.FileEnvWriter(config,
156                                            logger,
157                                            pathlauncher,
158                                            None,
159                                            env_info)
160
161     # Display some information
162     if display:
163         # Write the launcher file
164         logger.write(_("Generating launcher for %s :\n") % 
165                      src.printcolors.printcLabel(config.VARS.application), 1)
166         logger.write("  %s\n" % src.printcolors.printcLabel(filepath), 1)
167     
168     # Write the launcher
169     writer.write_env_file(filepath, 
170                           False,  # for launch
171                           shell,
172                           additional_env=additional_env,
173                           no_path_init=no_path_init)
174     
175
176     # ... and append the launch of the exe 
177     if cmd:
178         with open(filepath, "a") as exe_launcher:
179             exe_launcher.write(cmd)
180
181     # change the rights in order to make the file executable for everybody
182     os.chmod(filepath,
183              stat.S_IRUSR |
184              stat.S_IRGRP |
185              stat.S_IROTH |
186              stat.S_IWUSR |
187              stat.S_IXUSR |
188              stat.S_IXGRP |
189              stat.S_IXOTH)
190     return filepath
191
192
193 def generate_catalog(machines, config, logger):
194     """Generates an xml catalog file from a list of machines.
195     
196     :param machines List: The list of machines to add in the catalog   
197     :param config Config: The global configuration
198     :param logger Logger: The logger instance to use for the display 
199                           and logging
200     :return: The catalog file path.
201     :rtype: str
202     """
203     # remove empty machines
204     machines = map(lambda l: l.strip(), machines)
205     machines = filter(lambda l: len(l) > 0, machines)
206     
207     # log something
208     src.printcolors.print_value(logger, _("Generate Resources Catalog"),
209                                 ", ".join(machines), 4)
210     
211     # The command to execute on each machine in order to get some information
212     cmd = '"cat /proc/cpuinfo | grep MHz ; cat /proc/meminfo | grep MemTotal"'
213     user = getpass.getuser()
214
215     # Create the catalog path
216     catfile = src.get_tmp_filename(config, "CatalogResources.xml")
217     with open(catfile, 'w') as catalog:
218         # Write into it
219         catalog.write("<!DOCTYPE ResourcesCatalog>\n<resources>\n")
220         for k in machines:
221             if not src.architecture.is_windows(): 
222                 logger.write("    ssh %s " % (k + " ").ljust(20, '.'), 4)
223                 logger.flush()
224
225                 # Verify that the machine is accessible
226                 ssh_cmd = 'ssh -o "StrictHostKeyChecking no" %s %s' % (k, cmd)
227                 p = subprocess.Popen(ssh_cmd, shell=True,
228                         stdin=subprocess.PIPE,
229                         stdout=subprocess.PIPE,
230                         stderr=subprocess.PIPE)
231                 p.wait()
232
233                 machine_access = (p.returncode == 0) 
234                 if not machine_access: # The machine is not accessible
235                     logger.write(src.printcolors.printc(src.KO_STATUS) + "\n", 4)
236                     logger.write("    " + 
237                                  src.printcolors.printcWarning(p.stderr.read()), 2)
238                 else:
239                     # The machine is accessible, write the corresponding section on
240                     # the xml file
241                     logger.write(src.printcolors.printc(src.OK_STATUS) + "\n", 4)
242                     lines = p.stdout.readlines()
243                     freq = lines[0][:-1].split(':')[-1].split('.')[0].strip()
244                     nb_proc = len(lines) -1
245                     memory = lines[-1].split(':')[-1].split()[0].strip()
246                     memory = int(memory) / 1000
247
248             catalog.write("    <machine\n")
249             catalog.write("        protocol=\"ssh\"\n")
250             catalog.write("        nbOfNodes=\"1\"\n")
251             catalog.write("        mode=\"interactif\"\n")
252             catalog.write("        OS=\"LINUX\"\n")
253
254             if (not src.architecture.is_windows()) and machine_access :
255                 catalog.write("        CPUFreqMHz=\"%s\"\n" % freq)
256                 catalog.write("        nbOfProcPerNode=\"%s\"\n" % nb_proc)
257                 catalog.write("        memInMB=\"%s\"\n" % memory)
258
259             catalog.write("        userName=\"%s\"\n" % user)
260             catalog.write("        name=\"%s\"\n" % k)
261             catalog.write("        hostname=\"%s\"\n" % k)
262             catalog.write("    >\n")
263             catalog.write("    </machine>\n")
264
265         catalog.write("</resources>\n")
266     return catfile
267
268 def copy_catalog(config, catalog_path):
269     """Copy the xml catalog file into the right location
270     
271     :param config Config: The global configuration
272     :param catalog_path str: the catalog file path
273     :return: The environment dictionary corresponding to the file path.
274     :rtype: Dict
275     """
276     # Verify the existence of the file
277     if not os.path.exists(catalog_path):
278         raise IOError(_("Catalog not found: %s") % catalog_path)
279     # Get the application directory and copy catalog inside
280     out_dir = config.APPLICATION.workdir
281     new_catalog_path = os.path.join(out_dir, "CatalogResources.xml")
282     # Do the copy
283     if catalog_path != new_catalog_path:
284         shutil.copy(catalog_path, new_catalog_path)
285     additional_environ = {'USER_CATALOG_RESOURCES_FILE' : new_catalog_path}
286     return additional_environ
287
288
289
290 ##################################################
291
292 ##
293 # Describes the command
294 def description():
295     return _("The launcher command generates a SALOME launcher.\n\nexample:"
296              "\nsat launcher SALOME-master")
297
298 ##
299 # Runs the command.
300 def run(args, runner, logger):
301
302     # check for product
303     (options, args) = parser.parse_args(args)
304
305     # Verify that the command was called with an application
306     src.check_config_has_application( runner.cfg )
307     
308     # Determine the launcher name (from option, profile section or by default "salome")
309     if options.products is None:
310         environ_info = None
311     else:
312         # add products specified by user (only products 
313         # included in the application)
314         environ_info = filter(lambda l:
315                               l in runner.cfg.APPLICATION.products.keys(),
316                               options.products)
317     if options.name:
318         launcher_name = options.name
319     else:
320         launcher_name = src.get_launcher_name(runner.cfg)
321
322     no_path_initialisation=False
323     if options.no_path_init:
324         no_path_initialisation = True
325         
326     # set the launcher path
327     launcher_path = runner.cfg.APPLICATION.workdir
328
329     # Copy a catalog if the option is called
330     additional_environ = {}
331     if options.catalog:
332         additional_environ = copy_catalog(runner.cfg, options.catalog)
333
334     # Generate a catalog of resources if the corresponding option was called
335     if options.gencat:
336         catalog_path  = generate_catalog(options.gencat.split(","),
337                                          runner.cfg,
338                                          logger)
339         additional_environ = copy_catalog(runner.cfg, catalog_path)
340
341     # activate mesa use in the generated launcher
342     if options.use_mesa:
343         src.activate_mesa_property(runner.cfg)
344
345     # option -e has precedence over section profile
346     if not options.path_exe and src.get_launcher_exe(runner.cfg):
347         options.path_exe=src.get_launcher_exe(runner.cfg)
348
349     # Generate the launcher
350     generate_launch_file(runner.cfg,
351                          logger,
352                          launcher_name,
353                          launcher_path,
354                          options.path_exe,
355                          additional_env = additional_environ,
356                          env_info=environ_info,
357                          no_path_init = no_path_initialisation )
358
359     return 0