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