Salome HOME
manage extra environment files
[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 item in set(ref).difference(set(dic)):
148     result[item] = ref[item]
149   return result
150 #
151
152 def getConfigFileNames(args, checkExistence=False):
153   configOptionPrefix = "--config="
154   configArgs = [ str(x) for x in args if str(x).startswith(configOptionPrefix) ]
155   if len(configArgs) == 0:
156     configFileNames, unexist1 = __getConfigFileNamesDefault(), []
157   else:
158     # get configuration filenames
159     configFileNames, args, unexist1 = __getEnvironmentFileNames(args, configOptionPrefix, checkExistence)
160
161   # get extra environment
162   extraEnvFileNames, args, unexist2 = __getEnvironmentFileNames(args, "--extra_env=", checkExistence)
163   before = __get_environment_from_batch_command("env")
164   after = {}
165   for filename in extraEnvFileNames:
166     after.update(__get_environment_from_batch_command(filename))
167     pass
168
169   extraEnv = __subtract(after,before)
170   return configFileNames, extraEnv, args, unexist1+unexist2
171 #
172
173 def __getScriptPath(scriptName, searchPathList):
174   scriptName = os.path.expanduser(scriptName)
175   if os.path.isabs(scriptName):
176     return scriptName
177
178   if searchPathList is None or len(searchPathList) == 0:
179     return None
180
181   for path in searchPathList:
182     fullName = os.path.join(path, scriptName)
183     if os.path.isfile(fullName) or os.path.isfile(fullName+".py"):
184       return fullName
185
186   return None
187 #
188
189 class ScriptAndArgs:
190   # script: the command to be run, e.g. python <script.py>
191   # args: its input parameters
192   # out: its output parameters
193   def __init__(self, script=None, args=None, out=None):
194     self.script = script
195     self.args = args
196     self.out = out
197   #
198   def __repr__(self):
199     msg = "\n# Script: %s\n"%self.script
200     msg += "     * Input: %s\n"%self.args
201     msg += "     * Output: %s\n"%self.out
202     return msg
203   #
204 #
205 class ScriptAndArgsObjectEncoder(json.JSONEncoder):
206   def default(self, obj):
207     if isinstance(obj, ScriptAndArgs):
208       # to be easily parsed in GUI module (SalomeApp_Application)
209       # Do not export output arguments
210       return {obj.script:obj.args or []}
211     else:
212       return json.JSONEncoder.default(self, obj)
213 #
214
215 def getShortAndExtraArgs(args=[]):
216   try:
217     pos = args.index("--") # raise a ValueError if not found
218     short_args = args[:pos]
219     extra_args = args[pos:] # include "--"
220   except ValueError:
221     short_args = args
222     extra_args = []
223     pass
224
225   return short_args, extra_args
226 #
227
228 # Return an array of ScriptAndArgs objects
229 def getScriptsAndArgs(args=[], searchPathList=None):
230   short_args, extra_args = getShortAndExtraArgs(args)
231   args = short_args
232
233   if searchPathList is None:
234     searchPathList = sys.path
235
236   # Syntax of args: script.py [args:a1,a2=val,an] ... script.py [args:a1,a2=val,an]
237   scriptArgs = []
238   currentKey = None
239   argsPrefix = "args:"
240   outPrefix = "out:"
241   callPython = False
242   afterArgs = False
243   currentScript = None
244
245   for i in range(len(args)):
246     elt = os.path.expanduser(args[i])
247     isDriver = (elt == "driver") # special case for YACS scheme execution
248
249     if elt.startswith(argsPrefix):
250       if not currentKey or callPython:
251         raise SalomeContextException("args list must follow corresponding script file in command line.")
252       elt = elt.replace(argsPrefix, '')
253       scriptArgs[len(scriptArgs)-1].args = [os.path.expanduser(x) for x in elt.split(",")]
254       currentKey = None
255       callPython = False
256       afterArgs = True
257     elif elt.startswith(outPrefix):
258       if (not currentKey and not afterArgs) or callPython:
259         raise SalomeContextException("out list must follow both corresponding script file and its args in command line.")
260       elt = elt.replace(outPrefix, '')
261       scriptArgs[len(scriptArgs)-1].out = [os.path.expanduser(x) for x in elt.split(",")]
262       currentKey = None
263       callPython = False
264       afterArgs = False
265     elif elt.startswith("python"):
266       callPython = True
267       afterArgs = False
268     else:
269       if not os.path.isfile(elt) and not os.path.isfile(elt+".py"):
270         eltInSearchPath = __getScriptPath(elt, searchPathList)
271         if eltInSearchPath is None or (not os.path.isfile(eltInSearchPath) and not os.path.isfile(eltInSearchPath+".py")):
272           if elt[-3:] == ".py":
273             raise SalomeContextException("Script not found: %s"%elt)
274           scriptArgs.append(ScriptAndArgs(script=elt))
275           continue
276         elt = eltInSearchPath
277
278       if elt[-4:] != ".hdf":
279         if elt[-3:] == ".py" or isDriver:
280           currentScript = os.path.abspath(elt)
281         elif os.path.isfile(elt+".py"):
282           currentScript = os.path.abspath(elt+".py")
283         else:
284           currentScript = os.path.abspath(elt) # python script not necessary has .py extension
285         pass
286
287       if currentScript and callPython:
288         currentKey = "@PYTHONBIN@ "+currentScript
289         scriptArgs.append(ScriptAndArgs(script=currentKey))
290         callPython = False
291       elif currentScript:
292         if isDriver:
293           currentKey = currentScript
294           scriptArgs.append(ScriptAndArgs(script=currentKey))
295           callPython = False
296         elif not os.access(currentScript, os.X_OK):
297           currentKey = "@PYTHONBIN@ "+currentScript
298           scriptArgs.append(ScriptAndArgs(script=currentKey))
299         else:
300           ispython = False
301           try:
302             fn = open(currentScript)
303             for i in xrange(10): # read only 10 first lines
304               ln = fn.readline()
305               if re.search("#!.*python"):
306                 ispython = True
307                 break
308               pass
309             fn.close()
310           except:
311             pass
312           if not ispython and currentScript[-3:] == ".py":
313             currentKey = "@PYTHONBIN@ "+currentScript
314           else:
315             currentKey = currentScript
316             pass
317           scriptArgs.append(ScriptAndArgs(script=currentKey))
318       # CLOSE elif currentScript
319       afterArgs = False
320   # end for loop
321
322   if len(extra_args) > 1: # syntax: -- program [options] [arguments]
323     command = extra_args[1]
324     command_args = extra_args[2:]
325     scriptArgs.append(ScriptAndArgs(script=command, args=command_args))
326     pass
327
328   return scriptArgs
329 #
330
331 # Formatting scripts and args as a Bash-like command-line:
332 # script1.py [args] ; script2.py [args] ; ...
333 # scriptArgs is a list of ScriptAndArgs objects; their output parameters are omitted
334 def formatScriptsAndArgs(scriptArgs=None):
335     if scriptArgs is None:
336       return ""
337     commands = []
338     for sa_obj in scriptArgs:
339       cmd = sa_obj.script
340       if sa_obj.args:
341         cmd = " ".join([cmd]+sa_obj.args)
342       commands.append(cmd)
343
344     sep = " ; "
345     if sys.platform == "win32":
346       sep = " & "
347     command = sep.join(["%s"%x for x in commands])
348     return command
349 #
350
351 # Ensure OMNIORB_USER_PATH is defined. This variable refers to a folder in which
352 # SALOME will write omniOrb configuration files.
353 # If OMNIORB_USER_PATH is already set, only checks write access to associated directory ;
354 # an exception is raised if check fails. It allows users for choosing a specific folder.
355 # Else the function sets OMNIORB_USER_PATH this way:
356 # - If APPLI environment variable is set, OMNIORB_USER_PATH is set to ${APPLI}/USERS.
357 #   The function does not check USERS folder existence or write access. This folder
358 #   must exist ; this is the case if SALOME virtual application has been created using
359 #   appli_gen.py script.
360 # - Else OMNIORB_USER_PATH is set to user home directory.
361 def setOmniOrbUserPath():
362   omniorbUserPath = os.getenv("OMNIORB_USER_PATH")
363   if omniorbUserPath:
364     if not os.access(omniorbUserPath, os.W_OK):
365       raise Exception("Unable to get write access to directory: %s"%omniorbUserPath)
366     pass
367   else:
368     homePath = os.path.realpath(os.path.expanduser('~'))
369     #defaultOmniorbUserPath = os.path.join(homePath, ".salomeConfig/USERS")
370     defaultOmniorbUserPath = homePath
371     if os.getenv("APPLI"):
372       defaultOmniorbUserPath = os.path.join(homePath, os.getenv("APPLI"), "USERS")
373       pass
374     os.environ["OMNIORB_USER_PATH"] = defaultOmniorbUserPath
375 #
376
377 def getHostname():
378   return socket.gethostname().split('.')[0]
379 #