Salome HOME
981e69c5d8eeafe0ed9bc47de1500bbcecebe7b6
[tools/sat.git] / commands / environ.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 sys
21 import subprocess
22
23 import src
24
25 parser = src.options.Options()
26 parser.add_option('', 'shell', 'list2', 'shell',
27     _("Generates the environment files for the given format: bash (default), batch or all."), [])
28 parser.add_option('p', 'products', 'list2', 'products',
29     _("Includes only the specified products."))
30 parser.add_option('p', 'prefix', 'string', 'prefix',
31     _("Specifies the prefix for the environment files."), "env")
32 parser.add_option('t', 'target', 'string', 'out_dir',
33     _("Specifies the directory path where to put the environment files."), None)
34
35 # list of available shells with extensions
36 C_SHELLS = { "bash": "sh", "batch": "bat" }
37 C_ALL_SHELL = [ "bash", "batch" ]
38
39 class SalomeEnviron:
40     """Class to manage the environment of SALOME.
41     """
42
43     def __init__(self, cfg, environ, forBuild=False):
44         self.environ = environ
45         self.cfg = cfg
46         self.forBuild = forBuild
47         self.silent = False
48
49     def __repr__(self):
50         """easy non exhaustive quick resume for debug print"""
51         res={}
52         res["environ"]=str(self.environ)
53         res["forBuild"]=self.forBuild
54         return self.__class__.__name__ + str(res)[0:-1] + " ...etc...}"
55
56     def append(self, key, value, sep=os.pathsep):
57         return self.environ.append(key, value, sep)
58
59     def prepend(self, key, value, sep=os.pathsep):
60         return self.environ.prepend(key, value, sep)
61
62     def is_defined(self, key):
63         return self.environ.is_defined(key)
64
65     def get(self, key):
66         return self.environ.get(key)
67
68     def set(self, key, value):
69         # check if value needs to be evaluated
70         if value is not None and value.startswith("`") and value.endswith("`"):
71             res = subprocess.Popen("echo %s" % value, shell=True, stdout=subprocess.PIPE).communicate()
72             value = res[0].strip()
73
74         return self.environ.set(key, value)
75
76     def dump(self, out):
77         """Write the environment to out"""
78         for k in self.environ.environ.keys():
79             try:
80                 value = self.get(k)
81             except:
82                 value = "?"
83             out.write("%s=%s\n" % (k, value))
84
85     def add_line(self, nb_line):
86         if 'add_line' in dir(self.environ):
87             self.environ.add_line(nb_line)
88
89     def add_comment(self, comment):
90         if 'add_comment' in dir(self.environ):
91             self.environ.add_comment(comment)
92
93     def add_warning(self, warning):
94         if 'add_warning' in dir(self.environ):
95             self.environ.add_warning(warning)
96
97     def finish(self, required):
98         if 'finish' in dir(self.environ):
99             self.environ.add_line(1)
100             self.environ.add_comment("clean all the path")
101             self.environ.finish(required)
102
103     def list_version_4_prereq(self, prerequisite, logger):
104         alist = []
105         for path in self.cfg.TOOLS.environ.prereq_install_dir:
106             if not os.path.exists(path):
107                 continue
108             prereqlist = os.listdir(path)
109             for prereq in prereqlist:
110                 if prereq.split("-")[0] == prerequisite:
111                     #logger.error(str(prereq) + "\n")
112                     alist.append(str(prereq))
113
114         if len(alist) > 0:
115             logger.write(_("Available prerequisites are:") + "\n\t%s\n" % '\n\t'.join(alist), 2)
116
117     def set_python_libdirs(self):
118         if src.architecture.is_windows():
119             # sysconfig.get_python_lib() does not return appropriate path on Windows
120             # clearly decide here once for windows
121             ver = self.get('PYTHON_VERSION')
122             self.set('PYTHON_LIBDIR0', os.path.join('lib', 'python' + ver, 'site-packages'))
123             self.set('PYTHON_LIBDIR1', os.path.join('lib64', 'python' + ver, 'site-packages'))
124
125         else:
126             """obsolete: too hazardous for WHEN we have to interpret that throught new python script salomeContext
127             #less "' is clearer and safer for Popen etc...
128             cmd = 'python -c "import sys; from distutils import sysconfig; sys.stdout.write(sysconfig.get_python_lib(plat_specific=%s, standard_lib=False, prefix=str()))"'
129             #False==0 ; True==1
130             self.environ.command_value('PYTHON_LIBDIR0', cmd % "False")
131             self.environ.command_value('PYTHON_LIBDIR1', cmd % "True")
132             """
133             #cleary decide here once for linux
134             ver = self.get('PYTHON_VERSION')
135             self.set('PYTHON_LIBDIR0', os.path.join('lib', 'python' + ver, 'site-packages'))
136             self.set('PYTHON_LIBDIR1', os.path.join('lib64', 'python' + ver, 'site-packages'))
137           
138         self.python_lib0 = self.get('PYTHON_LIBDIR0')
139         self.python_lib1 = self.get('PYTHON_LIBDIR1')
140
141     ##
142     # Get the products name to add in SALOME_MODULES environment variable
143     # It is the name of the product, except in the case where the is a component name.
144     # And it has to be in SALOME_MODULES variable only if has_gui = "yes"
145     def getNames(self, lProducts):
146         lProdHasGui = [p for p in lProducts if 'has_gui' in src.product.get_product_config(self.cfg, p) and src.product.get_product_config(self.cfg, p).has_gui=='yes']
147         lProdName = []
148         for ProdName in lProdHasGui:
149             pi = src.product.get_product_config(self.cfg, ProdName)
150             if 'component_name' in pi:
151                 lProdName.append(pi.component_name)
152             else:
153                 lProdName.append(ProdName)
154         return lProdName
155
156     ##
157     # Sets the environment defined in the PRODUCT file.
158     def set_application_env(self, logger):
159         if 'environ' in self.cfg.APPLICATION:
160             self.add_comment("APPLICATION environment")
161             for p in self.cfg.APPLICATION.environ:
162                 self.set(p, self.cfg.APPLICATION.environ[p])
163             self.add_line(1)
164
165         if 'environ_script' in self.cfg.APPLICATION:
166             for pscript in self.cfg.APPLICATION.environ_script:
167                 self.add_comment("script %s" % pscript)
168                 sname = pscript.replace(" ", "_")
169                 self.run_env_script("APPLICATION_%s" % sname, self.cfg.APPLICATION.environ_script[pscript], logger)
170                 self.add_line(1)
171         
172         if 'profile' in self.cfg.APPLICATION:
173             profile_product = self.cfg.APPLICATION.profile.product
174             product_info_profile = src.product.get_product_config(self.cfg, profile_product)
175             profile_share_salome = os.path.join( product_info_profile.install_dir, "share", "salome" )
176             self.set( "SUITRoot", profile_share_salome )
177             self.set( "SalomeAppConfig", os.path.join( profile_share_salome, "resources", profile_product.lower() ) )
178         
179         # The list of products to launch
180         lProductsName = self.getNames(self.cfg.APPLICATION.products.keys())
181         
182         self.set( "SALOME_MODULES",    ','.join(lProductsName))
183
184     ##
185     # Set xxx_ROOT_DIR and xxx_SRC_DIR.
186     def set_salome_minimal_product_env(self, product_info, logger, single_dir, cfgdic=None):
187         # set root dir
188         root_dir = product_info.name + "_ROOT_DIR"
189         indic = cfgdic is not None and root_dir in cfgdic
190         if not self.is_defined(root_dir) and not indic:
191             if single_dir:
192                 self.set(root_dir, os.path.join(self.get('INSTALL_ROOT'), 'SALOME'))
193             elif 'install_dir' in product_info and product_info.install_dir:
194                 self.set(root_dir, product_info.install_dir)
195             elif not self.silent:
196                 logger.write("  " + _("No install_dir for product %s\n") % product_info.name, 5)
197
198         # set source dir, unless the product is fixed (no source dir)
199         if not src.product.product_is_fixed(product_info):
200             src_dir = product_info.name + "_SRC_DIR"
201             indic = cfgdic is not None and src_dir in cfgdic
202             if not self.is_defined(src_dir) and not indic:
203                 self.set(src_dir, product_info.source_dir)
204
205     def set_salome_generic_product_env(self, product):
206         pi = src.product.get_product_config(self.cfg, product)
207         env_root_dir = self.get(pi.name + "_ROOT_DIR")
208         l_binpath_libpath = []
209
210         # create additional ROOT_DIR for CPP components
211         if 'component_name' in pi:
212             compo_name = pi.component_name
213             if compo_name + "CPP" == product:
214                 compo_root_dir = compo_name + "_ROOT_DIR"
215                 envcompo_root_dir = os.path.join( self.cfg.TOOLS.common.install_root, compo_name )
216                 self.set(compo_root_dir ,  envcompo_root_dir)
217                 bin_path = os.path.join(envcompo_root_dir, 'bin', 'salome')
218                 lib_path = os.path.join(envcompo_root_dir, 'lib', 'salome')
219                 l_binpath_libpath.append( (bin_path, lib_path) )
220
221         appliname = 'salome'
222         if src.get_cfg_param(pi, 'product_type', 'SALOME').upper() not in [ "SALOME", "SMESH_PLUGIN", "SAMPLE" ]:
223             appliname = ''
224
225         bin_path = os.path.join(env_root_dir, 'bin', appliname)
226         lib_path = os.path.join(env_root_dir, 'lib', appliname)
227         l_binpath_libpath.append( (bin_path, lib_path) )
228
229         for bin_path, lib_path in l_binpath_libpath:
230             if not self.forBuild:
231                 self.prepend('PATH', bin_path)
232                 if src.architecture.is_windows():
233                     self.prepend('PATH', lib_path)
234                 else :
235                     self.prepend('LD_LIBRARY_PATH', lib_path)
236
237             l = [ bin_path, lib_path,
238                   os.path.join(env_root_dir, self.python_lib0, appliname),
239                   os.path.join(env_root_dir, self.python_lib1, appliname)
240                 ]
241             self.prepend('PYTHONPATH', l)
242
243     ##
244     # Loads environment define in the configuration.
245     def load_cfg_environment(self, cfg_env):
246         for env_def in cfg_env:
247             val = cfg_env[env_def]
248             if isinstance(val, src.pyconf.Mapping):
249                 continue
250
251             if isinstance(val, src.pyconf.Sequence):
252                 # transform into list of strings
253                 val = map(lambda l: l, val)
254
255             if env_def.startswith("_"):
256                 # separator exception for PV_PLUGIN_PATH
257                 if env_def[1:] == 'PV_PLUGIN_PATH':
258                     self.prepend(env_def[1:], val, ';')
259                 else:
260                     self.prepend(env_def[1:], val)
261             elif env_def.endswith("_"):
262                 # separator exception for PV_PLUGIN_PATH
263                 if env_def[:-1] == 'PV_PLUGIN_PATH':
264                     self.prepend(env_def[:-1], val, ';')
265                 else:
266                     self.prepend(env_def[:-1], val)
267             else:
268                 self.set(env_def, val)
269
270     ##
271     # Sets the environment of a product.
272     def set_a_product(self, product, logger, single_dir):
273                
274         if not self.silent:
275             logger.write(_("Setting environment for %s\n") % product, 4)
276
277         self.add_line(1)
278         self.add_comment('setting environ for ' + product)
279         
280         pi = src.product.get_product_config(self.cfg, product)
281
282         # Do not define environment if the product is native or fixed
283         if src.product.product_is_native(pi):
284             return
285
286         if "environ" in pi:
287             # set environment using definition of the product
288             self.set_salome_minimal_product_env(pi, logger, single_dir, pi.environ)
289             self.set_salome_generic_product_env(product)
290             self.load_cfg_environment(pi.environ)
291             if self.forBuild and "build" in pi.environ:
292                 self.load_cfg_environment(pi.environ.build)
293             if not self.forBuild and "launch" in pi.environ:
294                 self.load_cfg_environment(pi.environ.launch)
295         else:
296             # no environment defined in config
297             self.set_salome_minimal_product_env(pi, logger, single_dir)
298             if 'install_dir' in pi :
299                 self.set_salome_generic_product_env(product)
300
301         # if product_info defines a env_scripts load it
302         if 'env_script' in pi:
303             self.run_env_script(product, pi.env_script, logger)
304             
305
306     ##
307     # Runs an environment script.
308     def run_env_script(self, product, env_script, logger=None):
309         if not os.path.exists(env_script):
310             raise src.SatException(_("Environment script not found: %s") % env_script)
311
312         if not self.silent and logger is not None:
313             logger.write("  ** load %s\n" % env_script, 4)
314
315         try:
316             import imp
317             pyproduct = imp.load_source(product + "_env_script", env_script)
318             pyproduct.load_env(self)
319         except:
320             __, exceptionValue, exceptionTraceback = sys.exc_info()
321             print(exceptionValue)
322             import traceback
323             traceback.print_tb(exceptionTraceback)
324             traceback.print_exc()
325
326     ##
327     # Sets the environment for all the products.
328     def set_products(self, logger, src_root=None, single_dir=False):
329         self.add_line(1)
330         self.add_comment('setting environ for all products')
331
332         self.set_python_libdirs()
333
334         if src_root is None:
335             src_root = self.cfg.APPLICATION.workdir
336         self.set('SRC_ROOT', src_root)
337
338         appli_name = "APPLI"
339         if "APPLI" in self.cfg and "application_name" in self.cfg.APPLI:
340             appli_name = self.cfg.APPLI.application_name
341         self.set("SALOME_APPLI_ROOT", os.path.join(self.cfg.APPLICATION.workdir, appli_name))
342
343         if not single_dir:
344             single_dir = src.get_cfg_param(self.cfg.APPLICATION, "compil_in_single_dir", "no") == 'yes'
345
346         for product in src.get_cfg_param(self.cfg.APPLICATION, "imported_products", []):
347             self.set_a_product(product, logger, single_dir=single_dir)
348             self.finish(False)
349
350         for product in self.cfg.APPLICATION.products.keys():
351             self.set_a_product(product, logger, single_dir=single_dir)
352             self.finish(False)
353
354    
355     ##
356     # Sets the full environment for prerequisites and products specified in env_info dictionary.
357     def set_full_environ(self, logger, env_info):
358         # set product environ
359         self.set_application_env(logger)
360
361         # set products
362         self.set('INSTALL_ROOT', self.cfg.TOOLS.common.install_root)
363         self.set('SRC_ROOT', self.cfg.TOOLS.common.source_root)
364         self.set_python_libdirs()
365
366         single_dir = src.get_cfg_param(self.cfg.PRODUCT, "compil_in_single_dir", "no") == 'yes'
367         for product in env_info['products']:
368             self.set_a_product(product, logger, single_dir=single_dir)
369
370 ##
371 # Class to dump the environment to a file.
372 class FileEnvWriter:
373     def __init__(self, config, logger, out_dir, src_root, single_dir, env_info=None):
374         self.config = config
375         self.logger = logger
376         self.out_dir = out_dir
377         self.src_root= src_root
378         self.single_dir = single_dir
379         self.silent = True
380         self.env_info = env_info
381
382     def write_env_file(self, filename, forBuild, shell):
383         """Create an environment file."""
384         if not self.silent:
385             self.logger.write(_("Create environment file %s\n") % src.printcolors.printcLabel(filename), 3)
386
387         # create then env object
388         env_file = open(os.path.join(self.out_dir, filename), "w")
389         tmp = src.fileEnviron.get_file_environ(env_file, shell, {}, self.config )
390         env = SalomeEnviron(self.config, tmp, forBuild)
391         env.silent = self.silent
392
393         if self.env_info is not None:
394             env.set_full_environ(self.logger, self.env_info)
395         else:
396             # set env from PRODUCT
397             env.set_application_env(self.logger)
398             # set the products
399             env.set_products(self.logger,
400                             src_root=self.src_root, single_dir=self.single_dir)
401
402         # add cleanup and close
403         env.finish(True)
404         env_file.close()
405
406         return env_file.name
407
408     def write_product_file(self, filename, shell):
409         """Create a product file."""
410         if not self.silent:
411             self.logger.write(_("Create product file %s\n") % src.printcolors.printcLabel(filename), 3)
412
413         prod_file = open(os.path.join(self.out_dir, filename), "w")
414         if shell == "bash":
415             content = _bash_content % self.out_dir
416         elif shell == "batch":
417             content = _batch_content % self.out_dir
418         else:
419             raise src.SatException(_("Unknown shell: %s") % shell)
420
421         prod_file.write(content)
422         prod_file.close()
423        
424         return prod_file.name
425    
426     def write_cfgForPy_file(self, aFile, additional_env = {}):
427         """append to current opened aFile a cfgForPy environment (python syntax)."""
428         if not self.silent:
429             self.logger.write(_("Create configuration file %s\n") % src.printcolors.printcLabel(aFile.name), 3)
430
431         # create then env object
432         tmp = src.fileEnviron.get_file_environ(aFile, "cfgForPy", {}, self.config)
433         forBuild = True
434         forLaunch = False
435         env = SalomeEnviron(self.config, tmp, forLaunch)
436         env.silent = self.silent
437
438         if self.env_info is not None:
439             env.set_full_environ(self.logger, self.env_info)
440         else:
441             # set env from PRODUCT
442             env.set_application_env(self.logger)
443             # set the prerequisites
444             env.set_prerequisites(self.logger)
445             # set the products
446             env.set_products(self.logger,
447                             src_root=self.src_root, single_dir=self.single_dir)
448
449         if len(additional_env) != 0:
450             for variable in additional_env:
451                 env.set(variable, additional_env[variable])
452
453         # add cleanup and close
454         env.finish(True)
455
456
457 # create bash product file
458 _bash_content = """PRODUCT_DIR=%s
459 if [[ "${ENV_FOR_LAUNCH}x" == "x" ]]
460 then
461     export ENV_FOR_LAUNCH=1
462 fi
463
464 if [[ "${ENV_FOR_LAUNCH}" == "1" ]]
465 then
466     source $PRODUCT_DIR/env_launch.sh
467 else
468     source $PRODUCT_DIR/env_build.sh
469 fi
470 """
471
472 # create batch product file
473 _batch_content = """set PRODUCT_DIR=%s
474 IF NOT DEFINED ENV_FOR_LAUNCH set ENV_FOR_LAUNCH=1
475
476 if "%%ENV_FOR_LAUNCH%%"=="1" (
477     %%PRODUCT_DIR%%\\env_launch.bat
478 ) else (
479     %%PRODUCT_DIR%%\\env_build.bat
480 )
481 """
482
483 ##
484 # Definition of a Shell.
485 class Shell:
486     def __init__(self, name, extension):
487         self.name = name
488         self.extension = extension
489
490 ##
491 # Loads the environment (used to run the tests).
492 def load_environment(config, build, logger):
493     environ = SalomeEnviron(config, src.environment.Environ(os.environ), build)
494     environ.set_application_env(logger)
495     environ.set_prerequisites(logger)
496     environ.set_products(logger)
497     environ.finish(True)
498
499 ##
500 # Writes all the environment files
501 def write_all_source_files(config, logger, out_dir=None, src_root=None,
502                            single_dir=None, silent=False, shells=["bash"], prefix="env", env_info=None):
503     if not out_dir:
504         out_dir = config.APPLICATION.workdir
505
506     if not os.path.exists(out_dir):
507         raise src.SatException(_("Target directory not found: %s") % out_dir)
508
509     if not silent:
510         logger.write(_("Creating environment files for %s\n") % src.printcolors.printcLabel(config.APPLICATION.name), 2)
511         src.printcolors.print_value(logger, _("Target"), src.printcolors.printcInfo(out_dir), 3)
512         logger.write("\n", 3, False)
513     
514     shells_list = []
515     all_shells = C_ALL_SHELL
516     if "all" in shells:
517         shells = all_shells
518     else:
519         shells = filter(lambda l: l in all_shells, shells)
520
521     for shell in shells:
522         if shell not in C_SHELLS:
523             logger.write(_("Unknown shell: %s\n") % shell, 2)
524         else:
525             shells_list.append(Shell(shell, C_SHELLS[shell]))
526     
527     writer = FileEnvWriter(config, logger, out_dir, src_root, single_dir, env_info)
528     writer.silent = silent
529     files = []
530     for_build = True
531     for_launch = False
532     for shell in shells_list:
533         files.append(writer.write_env_file("%s_launch.%s" % (prefix, shell.extension), for_launch, shell.name))
534         files.append(writer.write_env_file("%s_build.%s" % (prefix, shell.extension),  for_build,  shell.name))
535
536     return files
537
538 ##################################################
539
540 ##
541 # Describes the command
542 def description():
543     return _("""The environ command generates the "
544                 "environment files of your application.""")
545
546 ##
547 # Runs the command.
548 def run(args, runner, logger):
549     (options, args) = parser.parse_args(args)
550
551     # check that the command was called with an application
552     src.check_config_has_application( runner.cfg )
553    
554     if options.products is None:
555         environ_info = None
556     else:
557         environ_info = {}
558
559         # add products specified by user (only products included in the application)
560         environ_info['products'] = filter(lambda l: l in runner.cfg.APPLICATION.products.keys(), options.products)
561     
562     if options.shell == []:
563         shell = ["bash"]
564         if src.architecture.is_windows():
565             shell = ["batch"]
566     else:
567         shell = options.shell
568     
569     write_all_source_files(runner.cfg, logger, out_dir=options.out_dir, shells=shell,
570                            prefix=options.prefix, env_info=environ_info)
571     logger.write("\n", 3, False)