Salome HOME
update SALOME scripts
[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 = "\nSALOME2 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_RedHat8_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         xml_file_name = "config.xml"
490         if os.path.exists("/etc/redhat-release"):
491             data = open("/etc/redhat-release").readline()
492             res = re.search(r'Red\s+Hat\s+Linux\s+release\s+([\d.]*)', data)
493             if res is not None:
494                 num = re.sub("[.]", "_", (res.groups())[0])
495                 filename = "config_RedHat" + num+ ".xml"
496                 if (os.path.exists(cur_dir + filename)):
497                     xml_file_name = filename
498             else:
499                 res = re.search(r'Mandrakelinux\s+release\s+([\d.]*)', data)
500                 if res is not None:
501                     num = re.sub("[.]", "_", (res.groups())[0])
502                     filename = "config_Mandrake" + num+ ".xml"
503                     if (os.path.exists(cur_dir + filename)):
504                         xml_file_name = filename
505         xml_file = cur_dir +  xml_file_name
506
507     if xml_file is None or not os.path.exists(xml_file):
508         error_exit("No xml file is found, try to run with options -f <xmlfile>")
509
510     if not os.access(xml_file, os.R_OK):
511         print "There is no read access for "+ xml_file
512         sys.exit(1)
513
514     #---- GUI ----------------
515     if is_gui : 
516         env = os.environ
517         if not env.has_key("PATH") :
518             env["PATH"] = ""
519         if not env.has_key("LD_LIBRARY_PATH") :
520             env["LD_LIBRARY_PATH"]=  ""
521
522         env["LD_LIBRARY_PATH"] =  ".:" +  env["LD_LIBRARY_PATH"]
523         env["PATH"] = ".:"+ env["PATH"]
524
525         sys.exit(os.system("cd " + cur_dir + "; ./bin/SALOME_InstallWizard " + xml_file +"&"))
526         
527         
528
529     #-----  TUI ---------------------
530
531     #print xml_file, target_dir, tmp_dir, is_gui
532
533     message("Parsing xml config file: " + xml_file)
534     filehandle = open(xml_file)
535     data = filehandle.read()
536     filehandle.close()
537     parser = ConfigParser()
538     parser.feed(data)
539     parser.close()
540
541     # definitions :
542     # map
543     what_to_do = { "install sources"  : "install_source",
544                    "install binaries" : "install_binary",
545                    "use native"       : "try_native",
546                    "not install"      : "try_preinstalled"}
547     # define tmp dir  -----------
548     if tmp_dir is None:
549         tmp_dir = parser.path.tmpdir
550     if tmp_dir is None or tmp_dir == "":
551         tmp_dir = "/tmp"
552     import random
553     tmp_dir = tmp_dir + "/INSTALLWORK" + str(random.randint(10000,100000))
554     root_path = ""
555     if not os.path.exists(tmp_dir):
556         message("Creating temporary directory: " + tmp_dir); root_path = create_dir(tmp_dir, 0755) ; 
557     if not os.path.exists(tmp_dir):
558         error_exit("Invalid temporary directory " + tmp_dir + ". Use -tmp key to set directory or correct xml file\n\n")
559
560     if not os.access(tmp_dir, os.W_OK) :
561         str = "There is no write permissions for directory " + tmp_dir + ". Use -tmp key to set temporary directory or correct xml file"
562         error_exit(str)
563         
564     # define target dir  --------
565     if target_dir is None:
566         target_dir = parser.path.targetdir
567
568     if not os.path.exists(target_dir):
569         message("Creating target directory: " + target_dir); create_dir(target_dir, 0755)
570     if not os.path.exists(target_dir):
571         error_exit("Invalid target directory " + target_dir + ". Use -t key to set directory or correct xml file\n\n")
572
573     if not os.access(target_dir, os.W_OK) :
574         str = "There is no write permissions for directory " + target_dir + ". Use -t key to set target directory or correct xml file."
575         error_exit(str)
576
577     # define products dir ------------
578     source_dir =  cur_dir + "Products" ; 
579     if not check_dir(source_dir):
580         remove_dir(root_path)
581         sys.exit(1)
582        
583     subdir = {"install sources"  : "SOURCES",
584               "install binaries" : "BINARIES/"+parser.config.os,
585               "use native"       : "",
586               "not install"      : ""}
587
588
589     #  define scripts dir ------------
590     scripts_dir = cur_dir + "config_files/"
591     if not check_dir(scripts_dir):
592         remove_dir(root_path)
593         sys.exit(1)
594     os.chdir(scripts_dir)
595
596     #list_of_dep =  create_levels(parser.products)
597     #list_of_dep =  get_dependencies_set(parser.products)
598     list_of_dep =  get_dependencies(parser.products)
599
600     message("Checking available disk space")
601     if check_disk_space(parser.products, scripts_dir, target_dir, tmp_dir) :
602
603         message("Starting...")
604         # install products
605         for product in parser.products :
606
607             if check_bool(product.disable): continue
608
609             message("Processing " + product.name + "...")
610             cmd = scripts_dir +  product.script + " " + \
611                   what_to_do[product.install]+ " " + \
612                   tmp_dir + " " + \
613                   source_dir + "/" + subdir[product.install] + " " + \
614                   target_dir + " " + \
615                   '"' + list_of_dep + '"' + " " + \
616                   product.name
617
618             res = os.system(cmd)
619             #if res : break; # try_preinstalled can return 1
620
621         # pickup environment
622         message("Creating environment files")
623         for product in parser.products :
624
625             if check_bool(product.disable): continue
626
627             if check_bool(product.pickupEnv):
628                 cmd = scripts_dir +  product.script + " " + \
629                       "pickup_env " + \
630                       tmp_dir + " " + \
631                       source_dir + "/" + subdir[product.install] + " " + \
632                       target_dir + " " + \
633                       '"' + list_of_dep + '"' + " " + \
634                       product.name
635                 
636                 res = os.system(cmd)
637
638     message("Cleaning temporary directory")
639     remove_dir(root_path)
640     message("Finished!")