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