Salome HOME
Merge branch 'mbs/auto_backup' of https://codev-tuleap.cea.fr/plugins/git/salome...
[modules/kernel.git] / bin / salomeContextUtils.py.in
1 # Copyright (C) 2013-2024  CEA, EDF, OPEN CASCADE
2 #
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU Lesser General Public
5 # License as published by the Free Software Foundation; either
6 # version 2.1 of the License, or (at your option) any later version.
7 #
8 # This library is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 # Lesser General Public License for more details.
12 #
13 # You should have received a copy of the GNU Lesser General Public
14 # License along with this library; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 #
17 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 #
19
20 import os
21 import sys
22 import glob
23 import subprocess
24 import re
25 import socket
26 import json
27
28 """
29 Define a specific exception class to manage exceptions related to SalomeContext
30 """
31 class SalomeContextException(Exception):
32   """Report error messages to the user interface of SalomeContext."""
33 #
34
35 def __listDirectory(path):
36   allFiles = []
37   for root, dirs, files in os.walk(path):
38     cfgFiles = glob.glob(os.path.join(root,'*.cfg'))
39     allFiles += cfgFiles
40
41   return allFiles
42 #
43
44 def __getConfigFileNamesDefault():
45   absoluteAppliPath = os.getenv('ABSOLUTE_APPLI_PATH','')
46   if not absoluteAppliPath:
47     return []
48
49   envdDir = absoluteAppliPath + '/env.d'
50   if not os.path.isdir(envdDir):
51     return []
52
53   return __listDirectory(envdDir)
54 #
55
56 def __getEnvironmentFileNames(args, optionPrefix, checkExistence):
57   # special case: extra configuration/environment files are provided by user
58   # Search for command-line argument(s) <optionPrefix>=file1,file2,..., filen
59   # Search for command-line argument(s) <optionPrefix>=dir1,dir2,..., dirn
60   configArgs = [ str(x) for x in args if str(x).startswith(optionPrefix) ]
61
62   args = [ x for x in args if not x.startswith(optionPrefix) ]
63   allLists = [ x.replace(optionPrefix, '') for x in configArgs ]
64
65   configFileNames = []
66   unexisting = []
67   for currentList in allLists:
68     elements = currentList.split(',')
69     for elt in elements:
70       elt = os.path.realpath(os.path.expanduser(elt))
71       if os.path.isdir(elt):
72         configFileNames += __listDirectory(elt)
73       else:
74         if checkExistence and not os.path.isfile(elt):
75           unexisting += [elt]
76         else:
77           configFileNames += [elt]
78
79   return configFileNames, args, unexisting
80 #
81
82 def getConfigFileNames(args, checkExistence=False):
83   configOptionPrefix = "--config="
84   configArgs = [ str(x) for x in args if str(x).startswith(configOptionPrefix) ]
85   if len(configArgs) == 0:
86     configFileNames, unexist = __getConfigFileNamesDefault(), []
87   else:
88     # get configuration filenames
89     configFileNames, args, unexist = __getEnvironmentFileNames(args, configOptionPrefix, checkExistence)
90
91   return configFileNames, args, unexist
92 #
93
94 def __getScriptPath(scriptName, searchPathList):
95   scriptName = os.path.expanduser(scriptName)
96   if os.path.isabs(scriptName):
97     return scriptName
98
99   if searchPathList is None or len(searchPathList) == 0:
100     return None
101
102   for path in searchPathList:
103     fullName = os.path.join(path, scriptName)
104     if os.path.isfile(fullName) or os.path.isfile(fullName+".py"):
105       return fullName
106
107   return None
108 #
109
110 class ScriptAndArgs:
111   # script: the command to be run, e.g. python <script.py>
112   # args: its input parameters
113   # out: its output parameters
114   def __init__(self, script=None, args=None, out=None):
115     self.script = script
116     self.args = args
117     self.out = out
118   #
119   def __repr__(self):
120     msg = "\n# Script: %s\n"%self.script
121     msg += "     * Input: %s\n"%self.args
122     msg += "     * Output: %s\n"%self.out
123     return msg
124   #
125 #
126 class ScriptAndArgsObjectEncoder(json.JSONEncoder):
127   def default(self, obj):
128     if isinstance(obj, ScriptAndArgs):
129       # to be easily parsed in GUI module (SalomeApp_Application)
130       # Do not export output arguments
131       return {obj.script:obj.args or []}
132     else:
133       return json.JSONEncoder.default(self, obj)
134 #
135
136 def getShortAndExtraArgs(args=None):
137   if args is None:
138     args = []
139   try:
140     pos = args.index("--") # raise a ValueError if not found
141     short_args = args[:pos]
142     extra_args = args[pos:] # include "--"
143   except ValueError:
144     short_args = args
145     extra_args = []
146     pass
147
148   return short_args, extra_args
149 #
150
151 # Return an array of ScriptAndArgs objects
152 def getScriptsAndArgs(args=None, searchPathList=None):
153   if args is None:
154     args = []
155   short_args, extra_args = getShortAndExtraArgs(args)
156   args = short_args
157
158   if searchPathList is None:
159     searchPathList = sys.path
160
161   # Syntax of args: script.py [args:a1,a2=val,an] ... script.py [args:a1,a2=val,an]
162   scriptArgs = []
163   currentKey = None
164   argsPrefix = "args:"
165   outPrefix = "out:"
166   callPython = False
167   afterArgs = False
168   currentScript = None
169
170   for i in range(len(args)):
171     elt = os.path.expanduser(args[i])
172     isDriver = (elt == "driver") # special case for YACS scheme execution
173
174     if elt.startswith(argsPrefix):
175       if not currentKey or callPython:
176         raise SalomeContextException("args list must follow corresponding script file in command line.")
177       elt = elt.replace(argsPrefix, '')
178       # Special process if some items of 'args:' list are themselves lists
179       # Note that an item can be a list, but not a list of lists...
180       # So we can have something like this:
181       # myscript.py args:['file1','file2'],val1,"done",[1,2,3],[True,False],"ok"
182       # 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'
183       # We have to split elt to obtain: ['[file1,file2]','val1','done','[1,2,3]','[True,False]','ok']
184       contains_list = re.findall('(\[[^\]]*\])', elt)
185       if contains_list:
186         extracted_args = []
187         x = elt.split(",")
188         # x is ['[file1', 'file2]', 'val1', 'done', '[1', '2', '3]', '[True', 'False]', 'ok']
189         list_begin_indices = [i for i in range(len(x)) if x[i].startswith('[')] # [0, 4, 7]
190         list_end_indices = [i for i in range(len(x)) if x[i].endswith(']')] # [1, 6, 8]
191         start = 0
192         for lbeg, lend in zip(list_begin_indices,list_end_indices): # [(0, 1), (4, 6), (7, 8)]
193           if lbeg > start:
194             extracted_args += x[start:lbeg]
195             pass
196           extracted_args += [','.join(x[lbeg:lend+1])]
197           start = lend+1
198           pass
199         if start < len(x):
200           extracted_args += x[start:len(x)]
201           pass
202         scriptArgs[len(scriptArgs)-1].args = extracted_args
203         pass
204       else: # a single split is enough
205         scriptArgs[len(scriptArgs)-1].args = [os.path.expanduser(x) for x in elt.split(",")]
206         pass
207       currentKey = None
208       callPython = False
209       afterArgs = True
210     elif elt.startswith(outPrefix):
211       if (not currentKey and not afterArgs) or callPython:
212         raise SalomeContextException("out list must follow both corresponding script file and its args in command line.")
213       elt = elt.replace(outPrefix, '')
214       scriptArgs[len(scriptArgs)-1].out = [os.path.expanduser(x) for x in elt.split(",")]
215       currentKey = None
216       callPython = False
217       afterArgs = False
218     elif elt.startswith("python"):
219       callPython = True
220       afterArgs = False
221     else:
222       file_extension = os.path.splitext(elt)[-1]
223       if not os.path.isfile(elt) and not os.path.isfile(elt+".py"):
224         eltInSearchPath = __getScriptPath(elt, searchPathList)
225         if eltInSearchPath is None or (not os.path.isfile(eltInSearchPath) and not os.path.isfile(eltInSearchPath+".py")):
226           if file_extension == ".py":
227             raise SalomeContextException("Script not found: %s"%elt)
228           scriptArgs.append(ScriptAndArgs(script=elt))
229           continue
230         elt = eltInSearchPath
231
232       if file_extension != ".hdf":
233         if file_extension == ".py" or isDriver:
234           currentScript = os.path.abspath(elt)
235         elif os.path.isfile(elt+".py"):
236           currentScript = os.path.abspath(elt+".py")
237         else:
238           currentScript = os.path.abspath(elt) # python script not necessary has .py extension
239         pass
240
241       if currentScript and callPython:
242         currentKey = "@PYTHONBIN@ "+currentScript
243         scriptArgs.append(ScriptAndArgs(script=currentKey))
244         callPython = False
245       elif currentScript:
246         script_extension = os.path.splitext(currentScript)[-1]
247         if isDriver:
248           currentKey = currentScript
249           scriptArgs.append(ScriptAndArgs(script=currentKey))
250           callPython = False
251         elif not os.access(currentScript, os.X_OK):
252           currentKey = "@PYTHONBIN@ "+currentScript
253           scriptArgs.append(ScriptAndArgs(script=currentKey))
254         else:
255           ispython = False
256           try:
257             fn = open(currentScript)
258             for i in range(10): # read only 10 first lines
259               ln = fn.readline()
260               if re.search("#!.*python"):
261                 ispython = True
262                 break
263               pass
264           except Exception:
265             pass
266           finally:
267             fn.close()
268           if not ispython and script_extension == ".py":
269             currentKey = "@PYTHONBIN@ "+currentScript
270           else:
271             currentKey = currentScript
272             pass
273           scriptArgs.append(ScriptAndArgs(script=currentKey))
274       # CLOSE elif currentScript
275       afterArgs = False
276   # end for loop
277
278   if len(extra_args) > 1: # syntax: -- program [options] [arguments]
279     command = extra_args[1]
280     command_args = extra_args[2:]
281     scriptArgs.append(ScriptAndArgs(script=command, args=command_args))
282     pass
283
284   return scriptArgs
285 #
286
287 # Formatting scripts and args as a Bash-like command-line:
288 # script1.py [args] ; script2.py [args] ; ...
289 # scriptArgs is a list of ScriptAndArgs objects; their output parameters are omitted
290 def formatScriptsAndArgs(scriptArgs=None, escapeSpaces=False):
291     if scriptArgs is None:
292       return ""
293     commands = []
294     for sa_obj in scriptArgs:
295       cmd = sa_obj.script
296       if escapeSpaces and cmd:
297         if sys.platform == "win32":
298           cmd = cmd.replace(' ', ' "', 1)
299           cmd = cmd + '"'
300         else:
301           cmd = cmd.replace(' ', '\ ').replace('\ ', ' ', 1)
302       if sa_obj.args:
303         cmd = " ".join([cmd]+sa_obj.args)
304       commands.append(cmd)
305
306     sep = " ; "
307     if sys.platform == "win32":
308       sep = " & "
309     command = sep.join(["%s"%x for x in commands])
310     return command
311 #
312
313 # Ensure OMNIORB_USER_PATH is defined. This variable refers to a folder in which
314 # SALOME will write omniOrb configuration files.
315 # If OMNIORB_USER_PATH is already set, only checks write access to associated directory ;
316 # an exception is raised if check fails. It allows users for choosing a specific folder.
317 # Else the function sets OMNIORB_USER_PATH this way:
318 # - If APPLI environment variable is set, and if ${APPLI}/USERS points at an existing 
319 #   folder with write access, then OMNIORB_USER_PATH is set to ${APPLI}/USERS.
320 #   This is the case if SALOME virtual application has been created using
321 #   appli_gen.py script.
322 # - Else OMNIORB_USER_PATH is set to user home directory.
323 def setOmniOrbUserPath():
324   omniorbUserPath = os.getenv("OMNIORB_USER_PATH")
325   if omniorbUserPath:
326     if not os.access(omniorbUserPath, os.W_OK):
327       raise Exception("Unable to get write access to directory: %s"%omniorbUserPath)
328     pass
329   else:
330     # Must be in /tmp (or equivalent) to handle application concurrency
331     try:
332       import tempfile
333       temp = tempfile.NamedTemporaryFile()
334       temp_dir = os.path.dirname(temp.name)
335       temp.close()
336       if not os.access(temp_dir, os.W_OK):
337         raise Exception("Unable to get write access to directory: %s"%temp_dir)
338       os.environ["OMNIORB_USER_PATH"] = temp_dir
339     except Exception:
340       homePath = os.path.realpath(os.path.expanduser('~'))
341       #defaultOmniorbUserPath = os.path.join(homePath, ".salomeConfig/USERS")
342       defaultOmniorbUserPath = homePath
343       if os.getenv("APPLI"):
344         appli_users_path=os.path.join(homePath, os.getenv("APPLI"), "USERS")
345         if os.access(appli_users_path, os.W_OK):
346           defaultOmniorbUserPath = appli_users_path
347         pass
348       os.environ["OMNIORB_USER_PATH"] = defaultOmniorbUserPath
349 #
350
351 def getHostname():
352   return socket.gethostname().split('.')[0]
353 #