Salome HOME
Merge branch 'V7_7_BR'
[modules/kernel.git] / bin / salomeContextUtils.py.in
1 #! /usr/bin/env python
2
3 # Copyright (C) 2013-2015  CEA/DEN, EDF R&D, OPEN CASCADE
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, or (at your option) any later version.
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 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
20 #
21
22 import os
23 import sys
24 import glob
25 import subprocess
26 import re
27 import socket
28 import json
29
30 """
31 Define a specific exception class to manage exceptions related to SalomeContext
32 """
33 class SalomeContextException(Exception):
34   """Report error messages to the user interface of SalomeContext."""
35 #
36
37 def __listDirectory(path):
38   allFiles = []
39   for root, dirs, files in os.walk(path):
40     cfgFiles = glob.glob(os.path.join(root,'*.cfg'))
41     allFiles += cfgFiles
42
43     shFiles = glob.glob(os.path.join(root,'*.sh'))
44     for f in shFiles:
45       no_ext = os.path.splitext(f)[0]
46       if not os.path.isfile(no_ext+".cfg"):
47         allFiles.append(f)
48
49   return allFiles
50 #
51
52 def __getConfigFileNamesDefault():
53   absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
54   if not absoluteAppliPath:
55     return []
56
57   envdDir = absoluteAppliPath + '/env.d'
58   if not os.path.isdir(envdDir):
59     return []
60
61   return __listDirectory(envdDir)
62 #
63
64 def __getEnvironmentFileNames(args, optionPrefix, checkExistence):
65   # special case: extra configuration/environment files are provided by user
66   # Search for command-line argument(s) <optionPrefix>=file1,file2,..., filen
67   # Search for command-line argument(s) <optionPrefix>=dir1,dir2,..., dirn
68   configArgs = [ str(x) for x in args if str(x).startswith(optionPrefix) ]
69
70   args = [ x for x in args if not x.startswith(optionPrefix) ]
71   allLists = [ x.replace(optionPrefix, '') for x in configArgs ]
72
73   configFileNames = []
74   unexisting = []
75   for currentList in allLists:
76     elements = currentList.split(',')
77     for elt in elements:
78       elt = os.path.realpath(os.path.expanduser(elt))
79       if os.path.isdir(elt):
80         configFileNames += __listDirectory(elt)
81       else:
82         if checkExistence and not os.path.isfile(elt):
83           unexisting += [elt]
84         else:
85           configFileNames += [elt]
86
87   return configFileNames, args, unexisting
88 #
89
90 def __validate_pair(ob):
91   try:
92     if not (len(ob) == 2):
93       #print "Unexpected result:", ob
94       raise ValueError
95   except:
96     return False
97   return True
98 #
99 def __get_environment_from_batch_command(env_cmd, initial=None):
100   """
101   Take a command (either a single command or list of arguments)
102   and return the environment created after running that command.
103   Note that if the command must be a batch file or .cmd file, or the
104   changes to the environment will not be captured.
105
106   If initial is supplied, it is used as the initial environment passed
107   to the child process.
108   """
109   #if not isinstance(env_cmd, (list, tuple)):
110   #    env_cmd = [env_cmd]
111   # construct the command that will alter the environment
112   #env_cmd = subprocess.list2cmdline(env_cmd)
113   # create a tag so we can tell in the output when the proc is done
114   tag = 'Done running command'
115   # construct a command to do accomplish this
116   cmd = '{env_cmd} && echo "{tag}"'.format(**vars())
117
118   # launch the process
119   proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=initial, shell=True)
120   # parse the output sent to stdout
121   lines = proc.stdout
122   # consume whatever output occurs until the tag is reached
123   #consume(itertools.takewhile(lambda l: tag not in l, lines))
124   # define a way to handle each KEY=VALUE line
125   handle_line = lambda l: l.rstrip().split('=',1)
126   # parse key/values into pairs
127   #pairs = map(handle_line, lines)
128   pairs = []
129   cpt = 0
130   while True:
131     line = lines.readline()
132     cpt = cpt+1
133     if tag in line or cpt > 1000:
134       break
135     if line:
136       pairs.append(line.rstrip().split('=',1))
137   # make sure the pairs are valid
138   valid_pairs = filter(__validate_pair, pairs)
139   # construct a dictionary of the pairs
140   result = dict(valid_pairs)
141   # let the process finish
142   proc.communicate()
143   return result
144 #
145 def __subtract(ref, dic):
146   result = {}
147   for key,val in ref.items():
148     if not dic.has_key(key):
149       result[key] = val
150     else:
151       # compare values types
152       if (type(dic[key]) != type(val)):
153         result[key] = val
154       else:
155         # compare values
156         if isinstance(val, basestring):
157           tolist1 = dic[key].split(os.pathsep)
158           tolist2 = val.split(os.pathsep)
159           diff = list(set(tolist2)-set(tolist1))
160           if diff:
161             result[key] = os.pathsep.join(diff)
162         else:
163           result[key] = val
164
165   return result
166 #
167
168 def getConfigFileNames(args, checkExistence=False):
169   configOptionPrefix = "--config="
170   configArgs = [ str(x) for x in args if str(x).startswith(configOptionPrefix) ]
171   if len(configArgs) == 0:
172     configFileNames, unexist1 = __getConfigFileNamesDefault(), []
173   else:
174     # get configuration filenames
175     configFileNames, args, unexist1 = __getEnvironmentFileNames(args, configOptionPrefix, checkExistence)
176
177   # get extra environment
178   extraEnvFileNames, args, unexist2 = __getEnvironmentFileNames(args, "--extra_env=", checkExistence)
179   before = __get_environment_from_batch_command("env")
180   after = {}
181   for filename in extraEnvFileNames:
182     after.update(__get_environment_from_batch_command(filename))
183     pass
184
185   extraEnv = __subtract(after,before)
186   return configFileNames, extraEnv, args, unexist1+unexist2
187 #
188
189 def __getScriptPath(scriptName, searchPathList):
190   scriptName = os.path.expanduser(scriptName)
191   if os.path.isabs(scriptName):
192     return scriptName
193
194   if searchPathList is None or len(searchPathList) == 0:
195     return None
196
197   for path in searchPathList:
198     fullName = os.path.join(path, scriptName)
199     if os.path.isfile(fullName) or os.path.isfile(fullName+".py"):
200       return fullName
201
202   return None
203 #
204
205 class ScriptAndArgs:
206   # script: the command to be run, e.g. python <script.py>
207   # args: its input parameters
208   # out: its output parameters
209   def __init__(self, script=None, args=None, out=None):
210     self.script = script
211     self.args = args
212     self.out = out
213   #
214   def __repr__(self):
215     msg = "\n# Script: %s\n"%self.script
216     msg += "     * Input: %s\n"%self.args
217     msg += "     * Output: %s\n"%self.out
218     return msg
219   #
220 #
221 class ScriptAndArgsObjectEncoder(json.JSONEncoder):
222   def default(self, obj):
223     if isinstance(obj, ScriptAndArgs):
224       # to be easily parsed in GUI module (SalomeApp_Application)
225       # Do not export output arguments
226       return {obj.script:obj.args or []}
227     else:
228       return json.JSONEncoder.default(self, obj)
229 #
230
231 def getShortAndExtraArgs(args=None):
232   if args is None:
233     args = []
234   try:
235     pos = args.index("--") # raise a ValueError if not found
236     short_args = args[:pos]
237     extra_args = args[pos:] # include "--"
238   except ValueError:
239     short_args = args
240     extra_args = []
241     pass
242
243   return short_args, extra_args
244 #
245
246 # Return an array of ScriptAndArgs objects
247 def getScriptsAndArgs(args=None, searchPathList=None):
248   if args is None:
249     args = []
250   short_args, extra_args = getShortAndExtraArgs(args)
251   args = short_args
252
253   if searchPathList is None:
254     searchPathList = sys.path
255
256   # Syntax of args: script.py [args:a1,a2=val,an] ... script.py [args:a1,a2=val,an]
257   scriptArgs = []
258   currentKey = None
259   argsPrefix = "args:"
260   outPrefix = "out:"
261   callPython = False
262   afterArgs = False
263   currentScript = None
264
265   for i in range(len(args)):
266     elt = os.path.expanduser(args[i])
267     isDriver = (elt == "driver") # special case for YACS scheme execution
268
269     if elt.startswith(argsPrefix):
270       if not currentKey or callPython:
271         raise SalomeContextException("args list must follow corresponding script file in command line.")
272       elt = elt.replace(argsPrefix, '')
273       # Special process if some items of 'args:' list are themselves lists
274       # Note that an item can be a list, but not a list of lists...
275       # So we can have something like this:
276       # myscript.py args:['file1','file2'],val1,"done",[1,2,3],[True,False],"ok"
277       # With such a call, an elt variable contains the string representing ['file1','file2'],val1,"done",[1,2,3],[True,False],"ok" that is '[file1,file2],val1,done,[1,2,3],[True,False],ok'
278       # We have to split elt to obtain: ['[file1,file2]','val1','done','[1,2,3]','[True,False]','ok']
279       contains_list = re.findall('(\[[^\]]*\])', elt)
280       if contains_list:
281         extracted_args = []
282         x = elt.split(",")
283         # x is ['[file1', 'file2]', 'val1', 'done', '[1', '2', '3]', '[True', 'False]', 'ok']
284         list_begin_indices = [i for i in xrange(len(x)) if x[i].startswith('[')] # [0, 4, 7]
285         list_end_indices = [i for i in xrange(len(x)) if x[i].endswith(']')] # [1, 6, 8]
286         start = 0
287         for lbeg, lend in zip(list_begin_indices,list_end_indices): # [(0, 1), (4, 6), (7, 8)]
288           if lbeg > start:
289             extracted_args += x[start:lbeg]
290             pass
291           extracted_args += [','.join(x[lbeg:lend+1])]
292           start = lend+1
293           pass
294         if start < len(x):
295           extracted_args += x[start:len(x)]
296           pass
297         scriptArgs[len(scriptArgs)-1].args = extracted_args
298         pass
299       else: # a single split is enough
300         scriptArgs[len(scriptArgs)-1].args = [os.path.expanduser(x) for x in elt.split(",")]
301         pass
302       currentKey = None
303       callPython = False
304       afterArgs = True
305     elif elt.startswith(outPrefix):
306       if (not currentKey and not afterArgs) or callPython:
307         raise SalomeContextException("out list must follow both corresponding script file and its args in command line.")
308       elt = elt.replace(outPrefix, '')
309       scriptArgs[len(scriptArgs)-1].out = [os.path.expanduser(x) for x in elt.split(",")]
310       currentKey = None
311       callPython = False
312       afterArgs = False
313     elif elt.startswith("python"):
314       callPython = True
315       afterArgs = False
316     else:
317       if not os.path.isfile(elt) and not os.path.isfile(elt+".py"):
318         eltInSearchPath = __getScriptPath(elt, searchPathList)
319         if eltInSearchPath is None or (not os.path.isfile(eltInSearchPath) and not os.path.isfile(eltInSearchPath+".py")):
320           if elt[-3:] == ".py":
321             raise SalomeContextException("Script not found: %s"%elt)
322           scriptArgs.append(ScriptAndArgs(script=elt))
323           continue
324         elt = eltInSearchPath
325
326       if elt[-4:] != ".hdf":
327         if elt[-3:] == ".py" or isDriver:
328           currentScript = os.path.abspath(elt)
329         elif os.path.isfile(elt+".py"):
330           currentScript = os.path.abspath(elt+".py")
331         else:
332           currentScript = os.path.abspath(elt) # python script not necessary has .py extension
333         pass
334
335       if currentScript and callPython:
336         currentKey = "@PYTHONBIN@ "+currentScript
337         scriptArgs.append(ScriptAndArgs(script=currentKey))
338         callPython = False
339       elif currentScript:
340         if isDriver:
341           currentKey = currentScript
342           scriptArgs.append(ScriptAndArgs(script=currentKey))
343           callPython = False
344         elif not os.access(currentScript, os.X_OK):
345           currentKey = "@PYTHONBIN@ "+currentScript
346           scriptArgs.append(ScriptAndArgs(script=currentKey))
347         else:
348           ispython = False
349           try:
350             fn = open(currentScript)
351             for i in xrange(10): # read only 10 first lines
352               ln = fn.readline()
353               if re.search("#!.*python"):
354                 ispython = True
355                 break
356               pass
357             fn.close()
358           except:
359             pass
360           if not ispython and currentScript[-3:] == ".py":
361             currentKey = "@PYTHONBIN@ "+currentScript
362           else:
363             currentKey = currentScript
364             pass
365           scriptArgs.append(ScriptAndArgs(script=currentKey))
366       # CLOSE elif currentScript
367       afterArgs = False
368   # end for loop
369
370   if len(extra_args) > 1: # syntax: -- program [options] [arguments]
371     command = extra_args[1]
372     command_args = extra_args[2:]
373     scriptArgs.append(ScriptAndArgs(script=command, args=command_args))
374     pass
375
376   return scriptArgs
377 #
378
379 # Formatting scripts and args as a Bash-like command-line:
380 # script1.py [args] ; script2.py [args] ; ...
381 # scriptArgs is a list of ScriptAndArgs objects; their output parameters are omitted
382 def formatScriptsAndArgs(scriptArgs=None):
383     if scriptArgs is None:
384       return ""
385     commands = []
386     for sa_obj in scriptArgs:
387       cmd = sa_obj.script
388       if sa_obj.args:
389         cmd = " ".join([cmd]+sa_obj.args)
390       commands.append(cmd)
391
392     sep = " ; "
393     if sys.platform == "win32":
394       sep = " & "
395     command = sep.join(["%s"%x for x in commands])
396     return command
397 #
398
399 # Ensure OMNIORB_USER_PATH is defined. This variable refers to a folder in which
400 # SALOME will write omniOrb configuration files.
401 # If OMNIORB_USER_PATH is already set, only checks write access to associated directory ;
402 # an exception is raised if check fails. It allows users for choosing a specific folder.
403 # Else the function sets OMNIORB_USER_PATH this way:
404 # - If APPLI environment variable is set, OMNIORB_USER_PATH is set to ${APPLI}/USERS.
405 #   The function does not check USERS folder existence or write access. This folder
406 #   must exist ; this is the case if SALOME virtual application has been created using
407 #   appli_gen.py script.
408 # - Else OMNIORB_USER_PATH is set to user home directory.
409 def setOmniOrbUserPath():
410   omniorbUserPath = os.getenv("OMNIORB_USER_PATH")
411   if omniorbUserPath:
412     if not os.access(omniorbUserPath, os.W_OK):
413       raise Exception("Unable to get write access to directory: %s"%omniorbUserPath)
414     pass
415   else:
416     homePath = os.path.realpath(os.path.expanduser('~'))
417     #defaultOmniorbUserPath = os.path.join(homePath, ".salomeConfig/USERS")
418     defaultOmniorbUserPath = homePath
419     if os.getenv("APPLI"):
420       defaultOmniorbUserPath = os.path.join(homePath, os.getenv("APPLI"), "USERS")
421       pass
422     os.environ["OMNIORB_USER_PATH"] = defaultOmniorbUserPath
423 #
424
425 def getHostname():
426   return socket.gethostname().split('.')[0]
427 #