Salome HOME
Merge from SALOME 2.x branch
[tools/install.git] / runInstall
1 #!/usr/bin/env python
2
3 import warnings
4 warnings.filterwarnings("ignore", "", DeprecationWarning)
5
6 import xmllib
7 import sys, os, string, re
8
9 #==============================================================
10 # get_help_info
11 #==============================================================
12 def get_help_info() :
13     str = "\nSALOME Installation Wizard\n\n"
14     str = str + "\tUsage : \n\tInstall [-g|b] [-f <xml-file>] [-t <target-dir>] [-tmp <tmp-dir>]\n"
15     str = str + "\n"
16     str = str + " -g              Runs the Installation Wizard in the GUI mode.\n"
17     str = str + "                 In this case only <xmlfile> is taken into account \n"
18     str = str + "                 from the parameters list. This key is used by default.\n"
19     str = str + "\n"
20     str = str + " -b              Runs the Installation Wizard in the batch mode.\n"
21     str = str + "                 All the found parameters are taken in to account.\n"
22     str = str + "\n"
23     str = str + " -f <xml-file>   The configuration file to be parsed by the Installation Wizard.\n" 
24     str = str + "                 If this parameter is missed then the script tries to define\n"
25     str = str + "                 the Red Hat version and use the corresponding xml. For example,\n"
26     str = str + "                 for Red Hat 8.0 config_RedHat_8.0.xml file is supposed to be used\n"
27     str = str + "                 by default. If the appropriate xml file is not found, the config.xml\n"
28     str = str + "                 is used by default.\n"
29     str = str + "\n"
30     str = str + " -t <target-dir> The target directory the products to be installed to.\n"
31     str = str + "                 This parameter overloads the target directory described in the\n"
32     str = str + "                 configuration file.\n"
33     str = str + "\n"
34     str = str + " -tmp <tmp-dir>  The directory which should be used for the temporary files.\n"
35     str = str + "                 This parameter overloads the temporary directory described in the\n"
36     str = str + "                 configuration file.\n"
37     str = str + "\n"
38     str = str + " -h              Prints this help information.\n" 
39     return str
40
41 #==============================================================
42 # message finction
43 #==============================================================
44 def message(msg):
45     print ">>>", msg
46     
47 #==============================================================
48 # error_exit
49 #==============================================================
50 def error_exit (str = ""):
51     import sys
52     if len(str): res = "\n" + str + "\n"
53     else : res = ""
54     print res + \
55           get_help_info() 
56     sys.exit(1);
57
58
59 #==============================================================
60 # Cheks whether the passed parameter is a key.
61 #==============================================================
62 def is_key ( val ):
63     import re
64     if val is not None : 
65         return re.match(r'^-[a-zA-Z]', val)
66     return 0
67
68 #==============================================================
69 # From the list of parameters extracts value following 'key' 
70 #==============================================================
71 def extract_parameter ( key, args ) :
72     import sys
73     length = len(args);
74     if ( length == 0 ) :  return None
75    
76     found = 0;
77
78     for i in range(0, length-1):
79         if  args[i] == key : 
80             if ( is_key ( args[i+1]) ) :
81                 print " No value after key ", key
82                 sys.exit(1);
83             
84             value = args[i+1]
85             if ( i < length - 2  and is_key ( args[i+2] ) == 0 ) : #control that only one value follows key 
86                                                               #(not a list). In this case params are correct.
87                 print "Too much values after key ", key
88                 sys.exit(1);
89             
90             found = 1; break;
91     
92     if (found) : 
93         return  value 
94     
95     return None
96
97
98 #===============================================================
99 # Extracts list of values following specified 'key' from 'args[]'
100 #===============================================================
101 def extract_list (key, args) : 
102
103     lenght = len(args)
104     if ( args is None or lenght == 0 ):
105         error_exit()
106
107     list=[]
108     found = 0
109
110     for i in  range(0, length) :
111         if args[i] == key  : 
112             if (is_key ( args[i+1]))  : 
113                  error_exit();
114             
115             for i in range (i+1, lenght):
116                 if is_key(args[i]) : break
117                 list.append(args[i])
118             found =1; break
119      
120     return list; #empty list is returned if no values after key
121
122
123 #==============================================================
124 # Method find the $key in the list and return 1 if success
125 # and 0 otherwise. 
126 #==============================================================
127 def find_key (key, argv) :
128
129     if (not is_key(key)) : return 0
130
131     for simbol in  argv :
132         if simbol == key:
133             return 1 
134     return 0
135
136 #==============================================================
137 # Parse the list of parameters
138 #==============================================================
139 def parse_parameters (args) :
140
141     if find_key('-h', args) :
142         print get_help_info();
143         import sys
144         sys.exit(0)
145         
146     xmlfile = extract_parameter("-f", args)
147     target_dir =  extract_parameter("-t", args)
148     tmp_dir = extract_parameter("-tmp", args)
149     if find_key('-b', args):
150         is_gui = 0
151     else : is_gui = 1
152     return [xmlfile, target_dir,  tmp_dir, is_gui]
153
154
155 #=================================================================
156 # Checks boolean value: yes/no, true/false, 1/0
157 #=================================================================
158 def check_bool(val):
159     return str(val).strip() in ["yes","true", "1"]
160
161 #=================================================================
162 # The first algorithm to create the dependencies list by their level
163 #=================================================================
164 def get_next_level(list, products):
165     
166     import re
167     result = []
168     expr = "(" + list[0].name
169     for i in range(1, len(list)):
170         expr = expr + "|"+ list[i].name
171     
172     expr = expr + ")$"
173     #expr=re.compile(expr)
174
175     for product in products:
176         deps = re.sub(r'\s+', "", product.dependencies)
177         if re.search(expr,  deps):
178             result.append(product)
179
180     return result
181
182
183 def create_levels(prods):
184     import copy
185     
186     products = copy.deepcopy(prods)
187     
188     result = {}
189     import re
190     #1. find the products with empty lists of dependencies
191     list = []
192     for product in products:
193         if len(re.sub(r'\s', "", product.dependencies)) == 0 :
194             list.append(product)
195
196     if len(list) == 0 :
197         raise RuntimeError, "Products that depend on nothing are not found"
198
199     # remove the first level products from the common list of products
200     for product in list :
201         products.remove(product)
202
203     ind = 0; 
204     result[0] = list
205
206     while (len(products)) :
207         res = get_next_level(list, products)
208         if len(res) == 0 :
209             raise RuntimeError, "Empty list of products is found"
210
211         for product in res :
212             products.remove(product)
213
214         ind = ind +1
215         result[ind] =  res
216         list = res
217             
218     str = ""
219     for i in result.keys():
220         for product in  result[i]:
221             str = str + product.name + " "
222
223     return str;
224             
225 #=================================================================
226 # The second algorithm
227 #=================================================================
228 def get_dependencies_set(prods) :
229     import copy
230     import re
231
232     products = copy.deepcopy(prods)
233     deps = ""
234     list = []
235
236     while (len(products)) :
237
238         tmplist = []
239         #find the products with empty list of dependencies
240         for product in products:
241             product.dependencies = re.sub(r'\s+$', "", product.dependencies)
242             product.dependencies = re.sub(r'^\s+', "", product.dependencies)
243            
244             if len(product.dependencies) == 0 :
245                tmplist.append(product); 
246                deps = deps + " " + product.name
247
248         list.append(tmplist)
249
250         #remove the products names from other products dependencies
251         for item in tmplist:
252             products.remove(item)
253
254             regexpr1 = "((^|,(\s+)?)"+item.name+"$|^"+item.name+"(\s+)?,(\s+)?)"
255             regexpr2 = ",(\s+)?"+item.name+"(\s+)?,(\s+)?"
256
257             for product in products:
258                 product.dependencies = re.sub(r'\s+$', "", product.dependencies)
259                 product.dependencies = re.sub(r'^\s+', "", product.dependencies)
260
261                 product.dependencies = re.sub(regexpr1, "", product.dependencies)
262                 product.dependencies = re.sub(regexpr2, ",", product.dependencies)
263
264     return deps 
265     
266 #=================================================================
267 # The third algorithm (same as SALOME_InstallWizard.cxx uses)
268 #=================================================================
269 def get_dependencies(prods) :
270     list = []
271     for product in prods:
272         if check_bool(product.disable): continue
273         
274         deps = product.dependencies.split(",")
275         for dep in deps:
276             if dep and not dep in list:
277                 list.append( dep )
278                 
279         if product and not product in list:
280             list.append( product.name )
281             
282     return " ".join( list )
283
284 #==============================================================
285 # Creates dir, returns the part of path that existed early.
286 # Access may be defined.
287 #==============================================================
288 def create_dir (directory, access = 0777):
289     import string, os
290     dirs = string.split(directory, "/")
291     existing = ""; dir = ""
292     root = ""
293     for item in dirs:
294         if len(item) == 0:  continue
295         dir = dir + "/"+item
296         if os.path.exists(dir):
297             existing = dir
298         else:
299             os.mkdir(dir, access )
300             #root= existing + "/"+item
301             if dir == existing + "/"+item :
302                 root = dir
303             #else : root = existing
304     
305     return root
306
307 #==============================================================
308 # class Product 
309 #==============================================================
310
311 class Product :
312     def __init__(self, theName,
313                  theVersion            = None,
314                  theInstall            = None,
315                  theSupportred         = None,
316                  theDisable            = None,
317                  theDependencies       = None,
318                  theInstalldiskspace   = None,
319                  theTemporarydiskspace = None,
320                  theScript             = None,
321                  thePickUpEnvironment  = None):
322         
323
324         self.name               = theName
325         self.version            = theVersion
326         self.install            = theInstall
327         self.supported          = theSupportred
328         self.disable            = theDisable
329         self.dependencies       = theDependencies
330         self.installdiskspace   = theInstalldiskspace
331         self.temporarydiskspace = theTemporarydiskspace
332         self.script             = theScript
333         self.pickupEnv          = thePickUpEnvironment
334
335 #===================================================================
336 # class Config
337 #===================================================================
338 class Config :
339     def __init__(self, theVersion='', theCaption='', theCopyright='', theLicense='', theOS=''):
340         self.version   = theVersion
341         self.caption   = theCaption
342         self.copyright = theCopyright
343         self.license   = theLicense
344         self.os        = theOS
345
346
347 #===================================================================
348 # class Path
349 #===================================================================
350 class Path :
351     def __init__(self, theTargetdir=".", theTmpdir="."):
352         self.targetdir = theTargetdir
353         self.tmpdir    = theTmpdir
354
355         
356 #===================================================================
357 # class ConfigParser
358 #===================================================================
359 class ConfigParser(xmllib.XMLParser):
360     def __init__(self):
361         xmllib.XMLParser.__init__(self)
362         self.products = []
363         self.currentdata = []
364         self.path = None
365         self.config = None
366         
367     def handle_data(self, data):
368         self.currentdata.append(data)
369         
370     def start_product(self, attrs):
371         aProduct = Product(attrs['name'],
372                            attrs['version'],
373                            attrs['install'],
374                            attrs['supported'],
375                            attrs['disable'],
376                            attrs['dependancies'],
377                            attrs['installdiskspace'],
378                            attrs['temporarydiskspace'],
379                            attrs['script'])
380
381         if attrs.has_key( 'pickupenv' ):
382             aProduct.pickupEnv = attrs['pickupenv']
383
384         self.products.append(aProduct)
385
386     def end_product(self):
387         pass
388
389     def start_config(self, attrs):
390         self.config = Config(attrs['version'],
391                              attrs['caption'],
392                              attrs['copyright'],
393                              attrs['license'],
394                              attrs['os'])
395     def end_config(self):
396         pass
397
398     def start_path (self, attrs):
399         self.path = Path(attrs['targetdir'],
400                          attrs['tempdir'])
401         
402     def end_path(self):
403         pass
404
405     def getProduct(self, prod):
406         for product in self.products:
407             if product.name == prod:
408                 return product
409         return None
410
411 #================================================================
412 # get the path using file name
413 #================================================================
414 def get_current_path(file_name):
415     path = "."; where = string.rfind(file_name,'/');
416     if (where != -1):
417         path = (file_name)[: where]
418         os.chdir(path);
419     path = os.getcwd() + "/"
420     return path
421
422 #================================================================
423 # checks dir existing 
424 #================================================================
425 def check_dir(dir):
426     if (os.path.islink(dir)) :
427         native_dir = os.readlink(dir)
428         if not os.path.exists(native_dir) :
429             print "Invalid link " + dir + ". The directory " + native_dir + " a link points to does not exist."
430             return 0 # problem
431     else :
432         if not os.path.exists(dir):
433             print "Directory " + dir + " does not exist"
434             return 0
435     return 1
436
437 #===============================================================
438 # Checks the disk space. Exit from interpreter if there is no
439 # enough disk space.
440 #===============================================================
441 def check_disk_space(products, script_dir, target_dir, tmp_dir):
442     import re, string, os
443     install_space = 0
444     temporary_space = 0
445     for product in products :
446         product.install = re.sub(r'^\s+', "", product.install)
447         product.install = re.sub(r'\s+$', "", product.install)
448         
449         if check_bool(product.disable) or product.install == "use native" or product.install == "not install":
450             continue
451         spaces = string.split( product.installdiskspace,',')
452         prod_space = spaces[0]
453         if (len(spaces) == 2 ) and (product.install == "install binaries") :
454             prod_space = spaces[1]
455         install_space = install_space + string.atoi(prod_space)
456         temporary_space = temporary_space + string.atoi(product.temporarydiskspace)
457     res = os.system(scripts_dir + "checkSize.sh" + " " + target_dir + " " + str(install_space))
458     if res:
459         print "There is no enough space to install the products."
460         return 0
461     
462     res = os.system(scripts_dir + "checkSize.sh" + " " + tmp_dir + " " + str(temporary_space))
463     if res:
464         print "There is no enough space for tmp directory."
465         return 0
466     
467     return 1
468  
469 #===============================================================
470 # Removes temporary directory
471 #===============================================================
472 def remove_dir( rem_path = "" ):
473     if len( rem_path ) and os.path.exists( rem_path ):
474         os.system( "rm -rf " + rem_path )
475     pass
476     
477 #================================================================
478 # main
479 #================================================================
480     
481 if __name__ == "__main__":
482     
483     cur_dir = get_current_path(sys.argv[0])
484    
485     [xml_file, target_dir, tmp_dir, is_gui]  = parse_parameters(sys.argv)
486
487     # define xml file -----------------
488     if (xml_file is None) :
489         plt_name = ""
490         plt_ver  = ""
491         xml_file_name = "config.xml"
492         if os.path.exists("/etc/redhat-release"):
493             # - Red Hat Linux 8.0
494             # - Red Hat Linux 9
495             # - Mandrake Linux 10.1
496             # - Scientific Linux 3.0.5
497             data = open("/etc/redhat-release").readline()
498             res = re.search(r'(.*)[L|l]inux.*release\s+([\d.]*)', data)
499             if res:
500                 plt_name = "".join(res.group(1).split())
501                 plt_ver  = res.group(2)
502         elif os.path.exists("/etc/debian_version"):
503             # - Debian 3.1
504             plt_name = "Debian"
505             plt_ver = open("/etc/debian_version").readline().strip()
506         _xml_file_name = "config_%s_%s.xml"%(plt_name, plt_ver)
507         if plt_name and plt_ver and os.path.exists(cur_dir + _xml_file_name):
508             xml_file_name = _xml_file_name
509         else:
510             print ""
511             print "Not supported Linux platform!"
512             print "Trying to use default configuration!"
513             print ""
514
515         xml_file = cur_dir +  xml_file_name
516
517     print xml_file
518     if not xml_file or not os.path.exists(xml_file):
519         msg  = "Configuration file %s is not found!"%xml_file
520         msg += "\nTry to run with -f <xmlfile> option."
521         error_exit(msg)
522
523     if not os.access(xml_file, os.R_OK):
524         print "There is no read access for %s file!"%xml_file
525         sys.exit(1)
526
527     #---- GUI ----------------
528     if is_gui : 
529         env = os.environ
530         if not env.has_key("PATH") :
531             env["PATH"] = ""
532         if not env.has_key("LD_LIBRARY_PATH") :
533             env["LD_LIBRARY_PATH"]=  ""
534
535         env["LD_LIBRARY_PATH"] =  ".:" +  env["LD_LIBRARY_PATH"]
536         env["PATH"] = ".:"+ env["PATH"]
537
538         sys.exit(os.system("cd " + cur_dir + "; ./bin/SALOME_InstallWizard " + xml_file +"&"))
539         
540         
541
542     #-----  TUI ---------------------
543
544     #print xml_file, target_dir, tmp_dir, is_gui
545
546     message("Parsing xml config file: " + xml_file)
547     filehandle = open(xml_file)
548     data = filehandle.read()
549     filehandle.close()
550     parser = ConfigParser()
551     parser.feed(data)
552     parser.close()
553
554     # definitions :
555     # map
556     what_to_do = { "install sources"  : "install_source",
557                    "install binaries" : "install_binary",
558                    "use native"       : "try_native",
559                    "not install"      : "try_preinstalled"}
560     # define tmp dir  -----------
561     if tmp_dir is None:
562         tmp_dir = parser.path.tmpdir
563     if tmp_dir is None or tmp_dir == "":
564         tmp_dir = "/tmp"
565     import random
566     tmp_dir = tmp_dir + "/INSTALLWORK" + str(random.randint(10000,100000))
567     root_path = ""
568     if not os.path.exists(tmp_dir):
569         message("Creating temporary directory: " + tmp_dir); root_path = create_dir(tmp_dir, 0755) ; 
570     if not os.path.exists(tmp_dir):
571         error_exit("Invalid temporary directory " + tmp_dir + ". Use -tmp key to set directory or correct xml file\n\n")
572
573     if not os.access(tmp_dir, os.W_OK) :
574         str = "There is no write permissions for directory " + tmp_dir + ". Use -tmp key to set temporary directory or correct xml file"
575         error_exit(str)
576         
577     # define target dir  --------
578     if target_dir is None:
579         target_dir = parser.path.targetdir
580
581     if not os.path.exists(target_dir):
582         message("Creating target directory: " + target_dir); create_dir(target_dir, 0755)
583     if not os.path.exists(target_dir):
584         error_exit("Invalid target directory " + target_dir + ". Use -t key to set directory or correct xml file\n\n")
585
586     if not os.access(target_dir, os.W_OK) :
587         str = "There is no write permissions for directory " + target_dir + ". Use -t key to set target directory or correct xml file."
588         error_exit(str)
589
590     # define products dir ------------
591     source_dir =  cur_dir + "Products" ; 
592     if not check_dir(source_dir):
593         remove_dir(root_path)
594         sys.exit(1)
595        
596     subdir = {"install sources"  : "SOURCES",
597               "install binaries" : "BINARIES/"+parser.config.os,
598               "use native"       : "",
599               "not install"      : ""}
600
601
602     #  define scripts dir ------------
603     scripts_dir = cur_dir + "config_files/"
604     if not check_dir(scripts_dir):
605         remove_dir(root_path)
606         sys.exit(1)
607     os.chdir(scripts_dir)
608
609     #list_of_dep =  create_levels(parser.products)
610     #list_of_dep =  get_dependencies_set(parser.products)
611     list_of_dep =  get_dependencies(parser.products)
612
613     message("Checking available disk space")
614     if check_disk_space(parser.products, scripts_dir, target_dir, tmp_dir) :
615
616         message("Starting...")
617         # install products
618         for product in parser.products :
619
620             if check_bool(product.disable): continue
621
622             message("Processing " + product.name + "...")
623             cmd = scripts_dir +  product.script + " " + \
624                   what_to_do[product.install]+ " " + \
625                   tmp_dir + " " + \
626                   source_dir + "/" + subdir[product.install] + " " + \
627                   target_dir + " " + \
628                   '"' + list_of_dep + '"' + " " + \
629                   product.name
630
631             res = os.system(cmd)
632             #if res : break; # try_preinstalled can return 1
633
634         # pickup environment
635         message("Creating environment files")
636         for product in parser.products :
637
638             if check_bool(product.disable): continue
639
640             if check_bool(product.pickupEnv):
641                 cmd = scripts_dir +  product.script + " " + \
642                       "pickup_env " + \
643                       tmp_dir + " " + \
644                       source_dir + "/" + subdir[product.install] + " " + \
645                       target_dir + " " + \
646                       '"' + list_of_dep + '"' + " " + \
647                       product.name
648                 
649                 res = os.system(cmd)
650
651     message("Cleaning temporary directory")
652     remove_dir(root_path)
653     message("Finished!")