Salome HOME
6a674de733f354b02b122128260996750dcbf5f1
[tools/sat.git] / src / product.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3 #  Copyright (C) 2010-2012  CEA/DEN
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.
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 '''In this file are implemented the methods 
19    relative to the product notion of salomeTools
20 '''
21
22 import os
23 import re
24
25 import src
26
27 AVAILABLE_VCS = ['git', 'svn', 'cvs']
28 config_expression = "^config-\d+$"
29
30 def get_product_config(config, product_name, with_install_dir=True):
31     '''Get the specific configuration of a product from the global configuration
32     
33     :param config Config: The global configuration
34     :param product_name str: The name of the product
35     :param with_install_dir boolean: If false, do not provide an install 
36                                      directory (at false only for internal use 
37                                      of the function get_install_dir)
38     :return: the specific configuration of the product
39     :rtype: Config
40     '''
41     
42     # Get the version of the product from the application definition
43     version = config.APPLICATION.products[product_name]
44     # if no version, then take the default one defined in the application
45     if isinstance(version, bool): 
46         version = config.APPLICATION.tag      
47     
48     # Define debug and dev modes
49     # Get the tag if a dictionary is given in APPLICATION.products for the
50     # current product 
51     debug = 'no'
52     dev = 'no'
53     base = 'maybe'
54     if isinstance(version, src.pyconf.Mapping):
55         dic_version = version
56         # Get the version/tag
57         if not 'tag' in dic_version:
58             version = config.APPLICATION.tag
59         else:
60             version = dic_version.tag
61         
62         # Get the debug if any
63         if 'debug' in dic_version:
64             debug = dic_version.debug
65         
66         # Get the dev if any
67         if 'dev' in dic_version:
68             dev = dic_version.dev
69         
70         # Get the dev if any
71         if 'base' in dic_version:
72             base = dic_version.base
73     
74     vv = version
75     # substitute some character with _ in order to get the correct definition
76     # in config.PRODUCTS. This is done because the pyconf tool does not handle
77     # the . and - characters 
78     for c in ".-": vv = vv.replace(c, "_")
79     
80     prod_info = None
81     if product_name in config.PRODUCTS:
82         # If it exists, get the information of the product_version
83         if "version_" + vv in config.PRODUCTS[product_name]:
84             # returns specific information for the given version
85             prod_info = config.PRODUCTS[product_name]["version_" + vv]
86             prod_info.section = "version_" + vv
87         # Get the standard informations
88         elif "default" in config.PRODUCTS[product_name]:
89             # returns the generic information (given version not found)
90             prod_info = config.PRODUCTS[product_name].default
91             prod_info.section = "default"
92         
93         # merge opt_depend in depend
94         if prod_info is not None and 'opt_depend' in prod_info:
95             for depend in prod_info.opt_depend:
96                 if depend in config.PRODUCTS:
97                     prod_info.depend.append(depend,'')
98         
99         # In case of a product get with a vcs, 
100         # put the tag (equal to the version)
101         if prod_info is not None and prod_info.get_source in AVAILABLE_VCS:
102             
103             if prod_info.get_source == 'git':
104                 prod_info.git_info.tag = version
105             
106             if prod_info.get_source == 'svn':
107                 prod_info.svn_info.tag = version
108             
109             if prod_info.get_source == 'cvs':
110                 prod_info.cvs_info.tag = version
111         
112         # In case of a fixed product, 
113         # define the install_dir (equal to the version)
114         if prod_info is not None and prod_info.get_source=="fixed":
115             prod_info.install_dir = version
116         
117         # Check if the product is defined as native in the application
118         if prod_info is not None:
119             if version == "native":
120                 prod_info.get_source = "native"
121             elif prod_info.get_source == "native":
122                 msg = _("The product %(prod)s has version %(ver)s but is "
123                         "declared as native in its definition" %
124                         { 'prod': prod_info.name, 'ver': version})
125                 raise src.SatException(msg)
126
127     # If there is no definition but the product is declared as native,
128     # construct a new definition containing only the get_source key
129     if prod_info is None and version == "native":
130         prod_info = src.pyconf.Config()
131         prod_info.name = product_name
132         prod_info.get_source = "native"
133     
134     # If prod_info is still None, it means that there is no product definition
135     # in the config. The user has to provide it.
136     if prod_info is None:
137         msg = _("No definition found for the product %s\n"
138             "Please create a %s.pyconf file." % (product_name, product_name))
139         raise src.SatException(msg)
140     
141     # Set the debug, dev and version keys
142     prod_info.debug = debug
143     prod_info.dev = dev
144     prod_info.version = version
145     
146     # Set the archive_info if the product is get in archive mode
147     if prod_info.get_source == "archive":
148         if not "archive_info" in prod_info:
149             prod_info.addMapping("archive_info",
150                                  src.pyconf.Mapping(prod_info),
151                                  "")
152         if "archive_name" not in prod_info.archive_info: 
153             arch_name = product_name + "-" + version + ".tar.gz"
154             arch_path = src.find_file_in_lpath(arch_name,
155                                                config.PATHS.ARCHIVEPATH)
156             if not arch_path:
157                 msg = _("Archive %(arch_name)s for %(prod_name)s not found:"
158                             "\n" % {"arch_name" : arch_name,
159                                      "prod_name" : prod_info.name}) 
160                 raise src.SatException(msg)
161             prod_info.archive_info.archive_name = arch_path
162         else:
163             if (os.path.basename(prod_info.archive_info.archive_name) == 
164                                         prod_info.archive_info.archive_name):
165                 arch_name = prod_info.archive_info.archive_name
166                 arch_path = src.find_file_in_lpath(
167                                             arch_name,
168                                             config.PATHS.ARCHIVEPATH)
169                 if not arch_path:
170                     msg = _("Archive %(arch_name)s for %(prod_name)s not found:"
171                                 "\n" % {"arch_name" : arch_name,
172                                          "prod_name" : prod_info.name}) 
173                     raise src.SatException(msg)
174                 prod_info.archive_info.archive_name = arch_path
175         
176     # If the product compiles with a script, check the script existence
177     # and if it is executable
178     if product_has_script(prod_info):
179         # Check the compil_script key existence
180         if "compil_script" not in prod_info:
181             msg = _("No compilation script found for the product %s\n"
182                 "Please provide a \"compil_script\" key in its definition." 
183                 % (product_name))
184             raise src.SatException(msg)
185         
186         # Get the path of the script
187         script = prod_info.compil_script
188         script_name = os.path.basename(script)
189         if script == script_name:
190             # Only a name is given. Search in the default directory
191             script_path = src.find_file_in_lpath(script_name,
192                                                  config.PATHS.PRODUCTPATH,
193                                                  "compil_scripts")
194             if not script_path:
195                 raise src.SatException(_("Compilation script not found: %s") % 
196                                    script_name)
197             prod_info.compil_script = script_path
198        
199         # Check that the script is executable
200         if not os.access(prod_info.compil_script, os.X_OK):
201             raise src.SatException(
202                     _("Compilation script cannot be executed: %s") % 
203                     prod_info.compil_script)
204     
205     # Get the full paths of all the patches
206     if product_has_patches(prod_info):
207         patches = []
208         for patch in prod_info.patches:
209             patch_path = patch
210             # If only a filename, then search for the patch in the PRODUCTPATH
211             if os.path.basename(patch_path) == patch_path:
212                 # Search in the PRODUCTPATH/patches
213                 patch_path = src.find_file_in_lpath(patch,
214                                                     config.PATHS.PRODUCTPATH,
215                                                     "patches")
216                 if not patch_path:
217                     msg = _("Patch %(patch_name)s for %(prod_name)s not found:"
218                             "\n" % {"patch_name" : patch,
219                                      "prod_name" : prod_info.name}) 
220                     raise src.SatException(msg)
221             patches.append(patch_path)
222         prod_info.patches = patches
223
224     # Get the full paths of the environment scripts
225     if product_has_env_script(prod_info):
226         env_script_path = prod_info.environ.env_script
227         # If only a filename, then search for the environment script 
228         # in the PRODUCTPATH/env_scripts
229         if os.path.basename(env_script_path) == env_script_path:
230             # Search in the PRODUCTPATH/env_scripts
231             env_script_path = src.find_file_in_lpath(
232                                             prod_info.environ.env_script,
233                                             config.PATHS.PRODUCTPATH,
234                                             "env_scripts")
235             if not env_script_path:
236                 msg = _("Environment script %(env_name)s for %(prod_name)s not "
237                         "found.\n" % {"env_name" : env_script_path,
238                                        "prod_name" : prod_info.name}) 
239                 raise src.SatException(msg)
240
241         prod_info.environ.env_script = env_script_path
242     
243     if with_install_dir: 
244         # The variable with_install_dir is at false only for internal use 
245         # of the function get_install_dir
246         
247         # Set the install_dir key
248         prod_info.install_dir = get_install_dir(config, base, version, prod_info)
249                 
250     return prod_info
251
252 def get_install_dir(config, base, version, prod_info):
253     '''Compute the installation directory of a given product 
254     
255     :param config Config: The global configuration
256     :param base str: This corresponds to the value given by user in its 
257                      application.pyconf for the specific product. If "yes", the
258                     user wants the product to be in base. If "no", he wants the
259                     product to be in the application workdir
260     :param version str: The version of the product
261     :param product_info Config: The configuration specific to 
262                                the product
263     
264     :return: The path of the product installation
265     :rtype: str
266     '''
267     install_dir = ""
268     in_base = False
269     if (("install_dir" in prod_info and prod_info.install_dir == "base") 
270                                                             or base == "yes"):
271         in_base = True
272     if (base == "no" or ("no_base" in config.APPLICATION 
273                          and config.APPLICATION.no_base == "yes")):
274         in_base = False
275     
276     if in_base:
277         install_dir = get_base_install_dir(config, prod_info, version)
278     else:
279         if "install_dir" not in prod_info or prod_info.install_dir == "base":
280             # Set it to the default value (in application directory)
281             install_dir = os.path.join(config.APPLICATION.workdir,
282                                                 "INSTALL",
283                                                 prod_info.name)
284         else:
285             install_dir = prod_info.install_dir
286
287     return install_dir
288
289 def get_base_install_dir(config, prod_info, version):
290     '''Compute the installation directory of a product in base 
291     
292     :param config Config: The global configuration
293     :param product_info Config: The configuration specific to 
294                                the product
295     :param version str: The version of the product    
296     :return: The path of the product installation
297     :rtype: str
298     '''    
299     base_path = src.get_base_path(config) 
300     prod_dir = os.path.join(base_path, prod_info.name + "-" + version)
301     if not os.path.exists(prod_dir):
302         return os.path.join(prod_dir, "config-1")
303     
304     exists, install_dir = check_config_exists(config, prod_dir, prod_info)
305     if exists:
306         return install_dir
307     
308     # Find the first config-<i> directory that is available in the product
309     # directory
310     found = False 
311     label = 1
312     while not found:
313         install_dir = os.path.join(prod_dir, "config-%i" % label)
314         if os.path.exists(install_dir):
315             label+=1
316         else:
317             found = True
318             
319     return install_dir
320
321 def check_config_exists(config, prod_dir, prod_info):
322     '''Verify that the installation directory of a product in a base exists
323        Check all the config-<i> directory and verify the sat-config.pyconf file
324        that is in it 
325     
326     :param config Config: The global configuration
327     :param prod_dir str: The product installation directory path 
328                          (without config-<i>)
329     :param product_info Config: The configuration specific to 
330                                the product
331     :return: True or false is the installation is found or not 
332              and if it is found, the path of the found installation
333     :rtype: (boolean, str)
334     '''   
335     # check if the directories or files of the directory corresponds to the 
336     # directory installation of the product
337     l_dir_and_files = os.listdir(prod_dir)
338     for dir_or_file in l_dir_and_files:
339         oExpr = re.compile(config_expression)
340         if not(oExpr.search(dir_or_file)):
341             # not config-<i>, not interesting
342             continue
343         # check if there is the file sat-config.pyconf file in the installation
344         # directory    
345         config_file = os.path.join(prod_dir, dir_or_file, src.CONFIG_FILENAME)
346         if not os.path.exists(config_file):
347             continue
348         
349         # If there is no dependency, it is the right path
350         if len(prod_info.depend)==0:
351             return True, os.path.join(prod_dir, dir_or_file)
352         
353         # check if there is the config described in the file corresponds the 
354         # dependencies of the product
355         config_corresponds = True    
356         compile_cfg = src.pyconf.Config(config_file)
357         for prod_dep in prod_info.depend:
358             # if the dependency is not in the config, 
359             # the config does not correspond
360             if prod_dep not in compile_cfg:
361                 config_corresponds = False
362                 break
363             else:
364                 prod_dep_info = get_product_config(config, prod_dep, False)
365                 # If the version of the dependency does not correspond, 
366                 # the config does not correspond
367                 if prod_dep_info.version != compile_cfg[prod_dep]:
368                     config_corresponds = False
369                     break
370         if config_corresponds:
371             return True, os.path.join(prod_dir, dir_or_file)
372     
373     return False, None
374             
375             
376     
377 def get_products_infos(lproducts, config):
378     '''Get the specific configuration of a list of products
379     
380     :param lproducts List: The list of product names
381     :param config Config: The global configuration
382     :return: the list of tuples 
383              (product name, specific configuration of the product)
384     :rtype: [(str, Config)]
385     '''
386     products_infos = []
387     # Loop on product names
388     for prod in lproducts:       
389         # Get the specific configuration of the product
390         prod_info = get_product_config(config, prod)
391         if prod_info is not None:
392             products_infos.append((prod, prod_info))
393         else:
394             msg = _("The %s product has no definition "
395                     "in the configuration.") % prod
396             raise src.SatException(msg)
397     return products_infos
398
399 def get_product_dependencies(config, product_info):
400     '''Get recursively the list of products that are 
401        in the product_info dependencies
402     
403     :param config Config: The global configuration
404     :param product_info Config: The configuration specific to 
405                                the product
406     :return: the list of products in dependence
407     :rtype: list
408     '''
409     if "depend" not in product_info or product_info.depend == []:
410         return []
411     res = []
412     for prod in product_info.depend:
413         if prod not in res:
414             res.append(prod)
415         prod_info = get_product_config(config, prod)
416         dep_prod = get_product_dependencies(config, prod_info)
417         for prod_in_dep in dep_prod:
418             if prod_in_dep not in res:
419                 res.append(prod_in_dep)
420     return res
421
422 def check_installation(product_info):
423     '''Verify if a product is well installed. Checks install directory presence
424        and some additional files if it is defined in the config 
425     
426     :param product_info Config: The configuration specific to 
427                                the product
428     :return: True if it is well installed
429     :rtype: boolean
430     '''       
431     install_dir = product_info.install_dir
432     if not os.path.exists(install_dir):
433         return False
434     if ("present_files" in product_info and 
435         "install" in product_info.present_files):
436         for file_relative_path in product_info.present_files.install:
437             file_path = os.path.join(install_dir, file_relative_path)
438             if not os.path.exists(file_path):
439                 return False
440     return True
441
442 def product_is_sample(product_info):
443     '''Know if a product has the sample type
444     
445     :param product_info Config: The configuration specific to 
446                                the product
447     :return: True if the product has the sample type, else False
448     :rtype: boolean
449     '''
450     if 'type' in product_info:
451         ptype = product_info.type
452         return ptype.lower() == 'sample'
453     else:
454         return False
455
456 def product_is_salome(product_info):
457     '''Know if a product is of type salome
458     
459     :param product_info Config: The configuration specific to 
460                                the product
461     :return: True if the product is salome, else False
462     :rtype: boolean
463     '''
464     if 'type' in product_info:
465         ptype = product_info.type
466         return ptype.lower() == 'salome'
467     else:
468         return False
469
470 def product_is_fixed(product_info):
471     '''Know if a product is fixed
472     
473     :param product_info Config: The configuration specific to 
474                                the product
475     :return: True if the product is fixed, else False
476     :rtype: boolean
477     '''
478     get_src = product_info.get_source
479     return get_src.lower() == 'fixed'
480
481 def product_is_native(product_info):
482     '''Know if a product is native
483     
484     :param product_info Config: The configuration specific to 
485                                the product
486     :return: True if the product is native, else False
487     :rtype: boolean
488     '''
489     get_src = product_info.get_source
490     return get_src.lower() == 'native'
491
492 def product_is_dev(product_info):
493     '''Know if a product is in dev mode
494     
495     :param product_info Config: The configuration specific to 
496                                the product
497     :return: True if the product is in dev mode, else False
498     :rtype: boolean
499     '''
500     dev = product_info.dev
501     return dev.lower() == 'yes'
502
503 def product_is_debug(product_info):
504     '''Know if a product is in debug mode
505     
506     :param product_info Config: The configuration specific to 
507                                the product
508     :return: True if the product is in debug mode, else False
509     :rtype: boolean
510     '''
511     debug = product_info.debug
512     return debug.lower() == 'yes'
513
514 def product_is_autotools(product_info):
515     '''Know if a product is compiled using the autotools
516     
517     :param product_info Config: The configuration specific to 
518                                the product
519     :return: True if the product is autotools, else False
520     :rtype: boolean
521     '''
522     build_src = product_info.build_source
523     return build_src.lower() == 'autotools'
524
525 def product_is_cmake(product_info):
526     '''Know if a product is compiled using the cmake
527     
528     :param product_info Config: The configuration specific to 
529                                the product
530     :return: True if the product is cmake, else False
531     :rtype: boolean
532     '''
533     build_src = product_info.build_source
534     return build_src.lower() == 'cmake'
535
536 def product_is_vcs(product_info):
537     '''Know if a product is download using git, svn or cvs (not archive)
538     
539     :param product_info Config: The configuration specific to 
540                                the product
541     :return: True if the product is vcs, else False
542     :rtype: boolean
543     '''
544     return product_info.get_source in AVAILABLE_VCS
545
546 def product_has_script(product_info):
547     '''Know if a product has a compilation script
548     
549     :param product_info Config: The configuration specific to 
550                                the product
551     :return: True if the product it has a compilation script, else False
552     :rtype: boolean
553     '''
554     if "build_source" not in product_info:
555         # Native case
556         return False
557     build_src = product_info.build_source
558     return build_src.lower() == 'script'
559
560 def product_has_env_script(product_info):
561     '''Know if a product has an environment script
562     
563     :param product_info Config: The configuration specific to 
564                                the product
565     :return: True if the product it has an environment script, else False
566     :rtype: boolean
567     '''
568     return "environ" in product_info and "env_script" in product_info.environ
569
570 def product_has_patches(product_info):
571     '''Know if a product has one or more patches
572     
573     :param product_info Config: The configuration specific to 
574                                the product
575     :return: True if the product has one or more patches
576     :rtype: boolean
577     '''
578     return "patches" in product_info and len(product_info.patches) > 0