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