Salome HOME
Merge branch 'rbe/fix-version-id'
[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       scriptArgs[len(scriptArgs)-1].args = [os.path.expanduser(x) for x in elt.split(",")]
274       currentKey = None
275       callPython = False
276       afterArgs = True
277     elif elt.startswith(outPrefix):
278       if (not currentKey and not afterArgs) or callPython:
279         raise SalomeContextException("out list must follow both corresponding script file and its args in command line.")
280       elt = elt.replace(outPrefix, '')
281       scriptArgs[len(scriptArgs)-1].out = [os.path.expanduser(x) for x in elt.split(",")]
282       currentKey = None
283       callPython = False
284       afterArgs = False
285     elif elt.startswith("python"):
286       callPython = True
287       afterArgs = False
288     else:
289       if not os.path.isfile(elt) and not os.path.isfile(elt+".py"):
290         eltInSearchPath = __getScriptPath(elt, searchPathList)
291         if eltInSearchPath is None or (not os.path.isfile(eltInSearchPath) and not os.path.isfile(eltInSearchPath+".py")):
292           if elt[-3:] == ".py":
293             raise SalomeContextException("Script not found: %s"%elt)
294           scriptArgs.append(ScriptAndArgs(script=elt))
295           continue
296         elt = eltInSearchPath
297
298       if elt[-4:] != ".hdf":
299         if elt[-3:] == ".py" or isDriver:
300           currentScript = os.path.abspath(elt)
301         elif os.path.isfile(elt+".py"):
302           currentScript = os.path.abspath(elt+".py")
303         else:
304           currentScript = os.path.abspath(elt) # python script not necessary has .py extension
305         pass
306
307       if currentScript and callPython:
308         currentKey = "@PYTHONBIN@ "+currentScript
309         scriptArgs.append(ScriptAndArgs(script=currentKey))
310         callPython = False
311       elif currentScript:
312         if isDriver:
313           currentKey = currentScript
314           scriptArgs.append(ScriptAndArgs(script=currentKey))
315           callPython = False
316         elif not os.access(currentScript, os.X_OK):
317           currentKey = "@PYTHONBIN@ "+currentScript
318           scriptArgs.append(ScriptAndArgs(script=currentKey))
319         else:
320           ispython = False
321           try:
322             fn = open(currentScript)
323             for i in xrange(10): # read only 10 first lines
324               ln = fn.readline()
325               if re.search("#!.*python"):
326                 ispython = True
327                 break
328               pass
329             fn.close()
330           except:
331             pass
332           if not ispython and currentScript[-3:] == ".py":
333             currentKey = "@PYTHONBIN@ "+currentScript
334           else:
335             currentKey = currentScript
336             pass
337           scriptArgs.append(ScriptAndArgs(script=currentKey))
338       # CLOSE elif currentScript
339       afterArgs = False
340   # end for loop
341
342   if len(extra_args) > 1: # syntax: -- program [options] [arguments]
343     command = extra_args[1]
344     command_args = extra_args[2:]
345     scriptArgs.append(ScriptAndArgs(script=command, args=command_args))
346     pass
347
348   return scriptArgs
349 #
350
351 # Formatting scripts and args as a Bash-like command-line:
352 # script1.py [args] ; script2.py [args] ; ...
353 # scriptArgs is a list of ScriptAndArgs objects; their output parameters are omitted
354 def formatScriptsAndArgs(scriptArgs=None):
355     if scriptArgs is None:
356       return ""
357     commands = []
358     for sa_obj in scriptArgs:
359       cmd = sa_obj.script
360       if sa_obj.args:
361         cmd = " ".join([cmd]+sa_obj.args)
362       commands.append(cmd)
363
364     sep = " ; "
365     if sys.platform == "win32":
366       sep = " & "
367     command = sep.join(["%s"%x for x in commands])
368     return command
369 #
370
371 # Ensure OMNIORB_USER_PATH is defined. This variable refers to a folder in which
372 # SALOME will write omniOrb configuration files.
373 # If OMNIORB_USER_PATH is already set, only checks write access to associated directory ;
374 # an exception is raised if check fails. It allows users for choosing a specific folder.
375 # Else the function sets OMNIORB_USER_PATH this way:
376 # - If APPLI environment variable is set, OMNIORB_USER_PATH is set to ${APPLI}/USERS.
377 #   The function does not check USERS folder existence or write access. This folder
378 #   must exist ; this is the case if SALOME virtual application has been created using
379 #   appli_gen.py script.
380 # - Else OMNIORB_USER_PATH is set to user home directory.
381 def setOmniOrbUserPath():
382   omniorbUserPath = os.getenv("OMNIORB_USER_PATH")
383   if omniorbUserPath:
384     if not os.access(omniorbUserPath, os.W_OK):
385       raise Exception("Unable to get write access to directory: %s"%omniorbUserPath)
386     pass
387   else:
388     homePath = os.path.realpath(os.path.expanduser('~'))
389     #defaultOmniorbUserPath = os.path.join(homePath, ".salomeConfig/USERS")
390     defaultOmniorbUserPath = homePath
391     if os.getenv("APPLI"):
392       defaultOmniorbUserPath = os.path.join(homePath, os.getenv("APPLI"), "USERS")
393       pass
394     os.environ["OMNIORB_USER_PATH"] = defaultOmniorbUserPath
395 #
396
397 def getHostname():
398   return socket.gethostname().split('.')[0]
399 #