Salome HOME
Merge branch 'BR_8_3' of https://codev-tuleap.cea.fr/plugins/git/spns/SAT into BR_8_3
[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
28 parser = src.options.Options()
29
30 parser.add_option('n', 'name', 'string', 'name', _('Optional: The name of the'
31                             ' launcher (default is '
32                             'APPLICATION.profile.launcher_name)'))
33 parser.add_option('c', 'catalog', 'string', 'catalog',
34     _('Optional: The resources catalog to use'))
35 parser.add_option('', 'gencat', 'string', 'gencat',
36     _("Optional: Create a resources catalog for the specified machines "
37       "(separated with ',') \n\tNOTICE: this command will ssh to retrieve"
38       " information to each machine in the list"))
39
40 def generate_launch_file(config,
41                          logger,
42                          env_info=None,
43                          pathlauncher=None,
44                          display=True,
45                          additional_env={}):
46     '''Generates the launcher file.
47     
48     :param config Config: The global configuration
49     :param logger Logger: The logger instance to use for the display 
50                           and logging
51     :param env_info str: The list of products to add in the files.
52     :param pathlauncher str: The path to the launcher to generate
53     :param src_root str: The path to the directory where the sources are
54     :param display boolean: If False, do not print anything in the terminal
55     :param additional_env dict: The dict giving additional 
56                                 environment variables
57     :return: The launcher file path.
58     :rtype: str
59     '''
60     # Get the application directory and the profile directory
61     out_dir = config.APPLICATION.workdir
62     profile = config.APPLICATION.profile
63     profile_install_dir = get_profile_dir(config)
64     
65     # Compute the default launcher path if it is not provided in pathlauncher
66     # parameter
67     if pathlauncher is None:
68         if platform.system() == "Windows" :
69             filepath = os.path.join( os.path.join( profile_install_dir,
70                                                    'bin',
71                                                    'salome' ),
72                                      profile['launcher_name'] )
73         else:
74             filepath = os.path.join( out_dir,
75                                      profile['launcher_name'] )
76     else:
77         filepath = os.path.join(pathlauncher, profile['launcher_name'])
78
79     # Remove the file if it exists in order to replace it
80     if os.path.exists(filepath):
81         os.remove(filepath)
82
83     # Add the APPLI variable
84     additional_env['APPLI'] = filepath
85
86     # Get the launcher template
87     withProfile = src.fileEnviron.withProfile.replace( "PROFILE_INSTALL_DIR",
88                                                        profile_install_dir )
89     before, after = withProfile.split(
90                                 "# here your local standalone environment\n")
91
92     # create an environment file writer
93     writer = src.environment.FileEnvWriter(config,
94                                            logger,
95                                            out_dir,
96                                            src_root=None,
97                                            env_info=env_info)
98
99     # Display some information
100     if display:
101         # Write the launcher file
102         logger.write(_("Generating launcher for %s :\n") % 
103                      src.printcolors.printcLabel(config.VARS.application), 1)
104         logger.write("  %s\n" % src.printcolors.printcLabel(filepath), 1)
105     
106     # open the file and write into it
107     launch_file = open(filepath, "w")
108     launch_file.write(before)
109     # Write
110     writer.write_cfgForPy_file(launch_file, additional_env=additional_env)
111     launch_file.write(after)
112     launch_file.close()
113     
114     # change the rights in order to make the file executable for everybody
115     os.chmod(filepath,
116              stat.S_IRUSR |
117              stat.S_IRGRP |
118              stat.S_IROTH |
119              stat.S_IWUSR |
120              stat.S_IXUSR |
121              stat.S_IXGRP |
122              stat.S_IXOTH)
123     return filepath
124
125 def generate_launch_link(config,
126                          logger,
127                          launcherPath,
128                          pathlauncher=None,
129                          display=True,
130                          packageLauncher=False):
131     '''Generates the launcher link that sources Python 
132        and call the actual launcher.
133     
134     :param config Config: The global configuration
135     :param logger Logger: The logger instance to use for the display 
136                           and logging
137     :param launcherPath str: The path to the launcher to call
138     :param pathlauncher str: The path to the launcher (link) to generate
139     :param display boolean: If False, do not print anything in the terminal
140     :param packageLauncher boolean: if True, use a relative path (for package)
141     :return: The launcher link file path.
142     :rtype: str
143     '''
144     if pathlauncher is None:
145         # Make an executable file that sources python, then launch the launcher
146         # produced by generate_launch_file method
147         sourceLauncher = os.path.join(config.APPLICATION.workdir,
148                                       config.APPLICATION.profile.launcher_name)
149     else:
150         sourceLauncher = os.path.join(pathlauncher,
151                                       config.APPLICATION.profile.launcher_name)
152
153     # Change the extension for the windows case
154     if platform.system() == "Windows" :
155             sourceLauncher += '.bat'
156
157     # display some information
158     if display:
159         logger.write(_("\nGenerating the executable that sources"
160                        " python and runs the launcher :\n") , 1)
161         logger.write("  %s\n" %src.printcolors.printcLabel(sourceLauncher), 1)
162
163     # open the file to write
164     f = open(sourceLauncher, "w")
165
166     # Write the set up of the environment
167     if platform.system() == "Windows" :
168         shell = 'bat'
169     else:
170         shell = 'bash'
171         
172     # Write the Python environment files
173     env = src.environment.SalomeEnviron( config, 
174                         src.fileEnviron.get_file_environ( f, shell, config ) )
175     env.set_a_product( "Python", logger)
176
177     # Write the call to the original launcher
178     f.write( "\n\n")
179     if packageLauncher:
180         cmd = os.path.join('${out_dir_Path}', launcherPath)
181     else:
182         cmd = launcherPath
183
184     if platform.system() == "Windows" :
185         cmd = 'python ' + cmd + ' %*'
186     else:
187         cmd = cmd + ' $*'
188     
189     f.write( cmd )
190     f.write( "\n\n")
191
192     # Write the cleaning of the environment
193     env.finish(True)
194
195     # Close new launcher
196     f.close()
197     os.chmod(sourceLauncher,
198              stat.S_IRUSR |
199              stat.S_IRGRP |
200              stat.S_IROTH |
201              stat.S_IWUSR |
202              stat.S_IWGRP |
203              stat.S_IWOTH |
204              stat.S_IXUSR |
205              stat.S_IXGRP |
206              stat.S_IXOTH)
207     return sourceLauncher
208
209 def generate_catalog(machines, config, logger):
210     """Generates an xml catalog file from a list of machines.
211     
212     :param machines List: The list of machines to add in the catalog   
213     :param config Config: The global configuration
214     :param logger Logger: The logger instance to use for the display 
215                           and logging
216     :return: The catalog file path.
217     :rtype: str
218     """
219     # remove empty machines
220     machines = map(lambda l: l.strip(), machines)
221     machines = filter(lambda l: len(l) > 0, machines)
222     
223     # log something
224     src.printcolors.print_value(logger, _("Generate Resources Catalog"),
225                                 ", ".join(machines), 4)
226     
227     # The command to execute on each machine in order to get some information
228     cmd = '"cat /proc/cpuinfo | grep MHz ; cat /proc/meminfo | grep MemTotal"'
229     user = getpass.getuser()
230
231     # Create the catalog path
232     catfile = src.get_tmp_filename(config, "CatalogResources.xml")
233     catalog = file(catfile, "w")
234     
235     # Write into it
236     catalog.write("<!DOCTYPE ResourcesCatalog>\n<resources>\n")
237     for k in machines:
238         logger.write("    ssh %s " % (k + " ").ljust(20, '.'), 4)
239         logger.flush()
240
241         # Verify that the machine is accessible
242         ssh_cmd = 'ssh -o "StrictHostKeyChecking no" %s %s' % (k, cmd)
243         p = subprocess.Popen(ssh_cmd, shell=True,
244                 stdin=subprocess.PIPE,
245                 stdout=subprocess.PIPE,
246                 stderr=subprocess.PIPE)
247         p.wait()
248
249         if p.returncode != 0: # The machine is not accessible
250             logger.write(src.printcolors.printc(src.KO_STATUS) + "\n", 4)
251             logger.write("    " + 
252                          src.printcolors.printcWarning(p.stderr.read()), 2)
253         else:
254             # The machine is accessible, write the corresponding section on
255             # the xml file
256             logger.write(src.printcolors.printc(src.OK_STATUS) + "\n", 4)
257             lines = p.stdout.readlines()
258             freq = lines[0][:-1].split(':')[-1].split('.')[0].strip()
259             nb_proc = len(lines) -1
260             memory = lines[-1].split(':')[-1].split()[0].strip()
261             memory = int(memory) / 1000
262
263             catalog.write("    <machine\n")
264             catalog.write("        protocol=\"ssh\"\n")
265             catalog.write("        nbOfNodes=\"1\"\n")
266             catalog.write("        mode=\"interactif\"\n")
267             catalog.write("        OS=\"LINUX\"\n")
268             catalog.write("        CPUFreqMHz=\"%s\"\n" % freq)
269             catalog.write("        nbOfProcPerNode=\"%s\"\n" % nb_proc)
270             catalog.write("        memInMB=\"%s\"\n" % memory)
271             catalog.write("        userName=\"%s\"\n" % user)
272             catalog.write("        name=\"%s\"\n" % k)
273             catalog.write("        hostname=\"%s\"\n" % k)
274             catalog.write("    >\n")
275             catalog.write("    </machine>\n")
276
277     catalog.write("</resources>\n")
278     catalog.close()
279     return catfile
280
281 def copy_catalog(config, catalog_path):
282     """Copy the xml catalog file into the right location
283     
284     :param config Config: The global configuration
285     :param catalog_path str: the catalog file path
286     :return: The environment dictionary corresponding to the file path.
287     :rtype: Dict
288     """
289     # Verify the existence of the file
290     if not os.path.exists(catalog_path):
291         raise IOError(_("Catalog not found: %s") % catalog_path)
292     # Compute the location where to copy the file
293     profile_dir = get_profile_dir(config)
294     new_catalog_path = os.path.join(profile_dir, "CatalogResources.xml")
295     # Do the copy
296     shutil.copy(catalog_path, new_catalog_path)
297     additional_environ = {'USER_CATALOG_RESOURCES_FILE' : new_catalog_path}
298     return additional_environ
299
300 def get_profile_dir(config):
301     """Get the profile directory from the config
302     
303     :param config Config: The global configuration
304     :return: The profile install directory
305     :rtype: Str
306     """
307     profile_name = config.APPLICATION.profile.product
308     profile_info = src.product.get_product_config(config, profile_name)
309     return profile_info.install_dir
310
311 def finish_profile_install(config, launcherPath):
312     """Add some symlinks required for SALOME
313     
314     :param config Config: The global configuration
315     :param launcherPath str: the launcher file path
316     """
317     # Create a USERS directory
318     profile_dir = get_profile_dir(config)
319     user_dir = os.path.join(profile_dir, 'USERS')
320     if not os.path.exists(user_dir):
321         os.makedirs(user_dir)
322     # change rights of USERS directory
323     os.chmod(user_dir,
324              stat.S_IRUSR |
325              stat.S_IRGRP |
326              stat.S_IROTH |
327              stat.S_IWUSR |
328              stat.S_IWGRP |
329              stat.S_IWOTH |
330              stat.S_IXUSR |
331              stat.S_IXGRP |
332              stat.S_IXOTH)
333
334     # create a link in root directory to the launcher
335     if platform.system() != "Windows" :
336         link_path = os.path.join(config.APPLICATION.workdir, 'salome')
337         if not os.path.exists(link_path):
338             try:
339                 os.symlink(launcherPath, link_path)
340             except OSError:
341                 os.remove(link_path)
342                 os.symlink(launcherPath, link_path)
343
344         link_path = os.path.join(profile_dir, 'salome')
345         relativeLauncherPath = "../../salome"
346         try:
347             os.symlink(relativeLauncherPath, link_path)
348         except OSError:
349             os.remove(link_path)
350             os.symlink(relativeLauncherPath, link_path)
351
352 ##################################################
353
354 ##
355 # Describes the command
356 def description():
357     return _("The launcher command generates a SALOME launcher.\n\nexample:"
358              "\nsat launcher SALOME-master")
359
360 ##
361 # Runs the command.
362 def run(args, runner, logger):
363
364     # check for product
365     (options, args) = parser.parse_args(args)
366
367     # Verify that the command was called with an application
368     src.check_config_has_application( runner.cfg )
369     
370     # Verify that the APPLICATION section has a profile section
371     src.check_config_has_profile( runner.cfg )
372
373     # Verify that the profile is installed
374     if not src.product.check_installation(
375                                 src.product.get_product_config(
376                                     runner.cfg,
377                                     runner.cfg.APPLICATION.profile.product)):
378         msg = _("The profile of the application is not correctly installed.")
379         logger.write(src.printcolors.printcError(msg), 1)
380         return 1
381
382     # Change the name of the file to create 
383     # if the corresponding option was called
384     if options.name:
385         runner.cfg.APPLICATION.profile['launcher_name'] = options.name
386
387     # Copy a catalog if the option is called
388     additional_environ = {}
389     if options.catalog:
390         additional_environ = copy_catalog(runner.cfg, options.catalog)
391
392     # Generate a catalog of resources if the corresponding option was called
393     if options.gencat:
394         catalog_path  = generate_catalog(options.gencat.split(","),
395                                          runner.cfg,
396                                          logger)
397         additional_environ = copy_catalog(runner.cfg, catalog_path)
398
399     # Generate the launcher
400     launcherPath = generate_launch_file( runner.cfg,
401                                          logger,
402                                          additional_env = additional_environ )
403
404     if platform.system() == "Windows" :
405         # Create the link (bash file that sources python and then call 
406         # the actual launcher) to the launcher
407         generate_launch_link( runner.cfg, logger, launcherPath)
408
409     # Add some links
410     finish_profile_install(runner.cfg, launcherPath)
411
412     return 0