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