Salome HOME
spns #40779: implement git multiserver approach: github, gitpub, tuleap
[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
19 """\
20 In this file are implemented the methods 
21 relative to the product notion of salomeTools
22 """
23
24 import os
25 import re
26 import pprint as PP
27
28 import src
29 import src.debug as DBG
30 import src.versionMinorMajorPatch as VMMP
31
32 AVAILABLE_VCS = ['git', 'svn', 'cvs']
33
34 CONFIG_FILENAME = "sat-config-" # trace product depends version(s)
35 PRODUCT_FILENAME = "sat-product-" # trace product compile config
36 config_expression = "^config-\d+$"
37
38 def get_product_config(config, product_name, with_install_dir=True):
39     """Get the specific configuration of a product from the global configuration
40     
41     :param config Config: The global configuration
42     :param product_name str: The name of the product
43     :param with_install_dir boolean: If false, do not provide an install 
44                                      directory (at false only for internal use 
45                                      of the function check_config_exists)
46     :return: the specific configuration of the product
47     :rtype: Config
48     """
49
50     # Get the version of the product from the application definition
51     version = config.APPLICATION.products[product_name]
52     
53     # Define debug and dev modes
54     # Get the tag if a dictionary is given in APPLICATION.products for the
55     # current product 
56     debug = 'no'
57     dev = 'no'
58     hpc = 'no'
59     verbose = 'no'
60     base = 'maybe'
61     section = None
62
63     # if no version, then take the default one defined in the application
64     if isinstance(version, bool) or isinstance(version, str): 
65         # in this case tag is mandatory, not debug, verbose, dev
66         if 'debug' in config.APPLICATION:
67             debug = config.APPLICATION.debug
68         if 'verbose' in config.APPLICATION:
69             verbose = config.APPLICATION.verbose
70         if 'dev' in config.APPLICATION:
71             dev = config.APPLICATION.dev
72         if 'hpc' in config.APPLICATION:
73             hpc = config.APPLICATION.hpc
74         if 'base' in config.APPLICATION:
75             base = config.APPLICATION.base
76
77     # special case for which only the product name is mentionned 
78     if isinstance(version, bool):
79         version = config.APPLICATION.tag
80
81     if isinstance(version, src.pyconf.Mapping):
82         dic_version = version
83         # Get the version/tag
84         if not 'tag' in dic_version:
85             version = config.APPLICATION.tag
86         else:
87             version = dic_version.tag
88         
89         # Get the debug if any
90         if 'debug' in dic_version:
91             debug = dic_version.debug
92         elif 'debug' in config.APPLICATION:
93             debug = config.APPLICATION.debug
94         
95         # Get the verbose if any
96         if 'verbose' in dic_version:
97             verbose = dic_version.verbose
98         elif 'verbose' in config.APPLICATION:
99             verbose = config.APPLICATION.verbose
100         
101         # Get the dev if any
102         if 'dev' in dic_version:
103             dev = dic_version.dev
104         elif 'dev' in config.APPLICATION:
105             dev = config.APPLICATION.dev
106         
107         # Get the hpc if any
108         if 'hpc' in dic_version:
109             hpc = dic_version.hpc
110         elif 'hpc' in config.APPLICATION:
111             hpc = config.APPLICATION.hpc
112
113         # Get the base if any
114         if 'base' in dic_version:
115             base = dic_version.base
116         elif 'base' in config.APPLICATION:
117             base = config.APPLICATION.base
118
119         # Get the section if any
120         if 'section' in dic_version:
121             section = dic_version.section
122     
123     # this case occur when version is overwritten, cf sat # 8897
124     if isinstance(version, dict): 
125         dic_version = version
126         # Get the version/tag
127         if not 'tag' in dic_version:
128             version = config.APPLICATION.tag
129         else:
130             version = dic_version["tag"]
131         
132         # Get the debug if any
133         if 'debug' in dic_version:
134             debug = dic_version["debug"]
135         elif 'debug' in config.APPLICATION:
136             debug = config.APPLICATION.debug
137         
138         # Get the verbose if any
139         if 'verbose' in dic_version:
140             verbose = dic_version["verbose"]
141         elif 'verbose' in config.APPLICATION:
142             verbose = config.APPLICATION.verbose
143         
144         # Get the dev if any
145         if 'dev' in dic_version:
146             dev = dic_version["dev"]
147         elif 'dev' in config.APPLICATION:
148             dev = config.APPLICATION.dev
149         
150         # Get the hpc if any
151         if 'hpc' in dic_version:
152             hpc = dic_version['hpc']
153         elif 'hpc' in config.APPLICATION:
154             hpc = config.APPLICATION.hpc
155
156         # Get the base if any
157         if 'base' in dic_version:
158             base = dic_version["base"]
159
160         # Get the section if any
161         if 'section' in dic_version:
162             section = dic_version['section']
163
164     vv = version
165     # substitute some character with _ in order to get the correct definition
166     # in config.PRODUCTS. This is done because the pyconf tool does not handle
167     # the . and - characters 
168     for c in ".-/": vv = vv.replace(c, "_")
169
170     prod_info = None
171     if product_name in config.PRODUCTS:
172         # Search for the product description in the configuration
173         prod_info = get_product_section(config, product_name, vv, section)
174         
175         # get salomeTool version
176         prod_info.sat_version = src.get_salometool_version(config)
177
178         # merge opt_depend in depend
179         if prod_info is not None and 'opt_depend' in prod_info:
180             for depend in prod_info.opt_depend:
181                 if (depend in config.APPLICATION.products) and (depend not in prod_info.depend) :
182                     prod_info.depend.append(depend,'')
183         
184
185         # In case of a product get with a vcs, 
186         # put the tag (equal to the version)
187         if prod_info is not None and prod_info.get_source in AVAILABLE_VCS:
188             
189             if prod_info.get_source == 'git':
190                 prod_info.git_info.tag = version
191             
192             if prod_info.get_source == 'svn':
193                 prod_info.svn_info.tag = version
194             
195             if prod_info.get_source == 'cvs':
196                 prod_info.cvs_info.tag = version
197         
198         # In case of a fixed product, 
199         # define the install_dir (equal to the version)
200         if prod_info is not None and \
201            (os.path.isdir(version) or version.startswith("/")):
202            # we consider a (fixed) path  existing paths; 
203            # or paths starting with '/' (the objective is to print a correct 
204            # message to the user in case of non existing path.)
205             prod_info.install_dir = version
206             prod_info.get_source = "fixed"
207             prod_info.install_mode = "fixed"
208         
209         # Check if the product is defined as native in the application
210         if prod_info is not None:
211             if version == "native":
212                 prod_info.get_source = "native"
213             elif prod_info.get_source == "native":
214                 msg = _("The product %(prod)s has version %(ver)s but is "
215                         "declared as native in its definition" %
216                         { 'prod': prod_info.name, 'ver': version})
217                 raise src.SatException(msg)
218
219     # If there is no definition but the product is declared as native,
220     # construct a new definition containing only the get_source key
221     if prod_info is None and version == "native":
222         prod_info = src.pyconf.Config()
223         prod_info.name = product_name
224         prod_info.get_source = "native"
225
226     # If there is no definition but the product is fixed,
227     # construct a new definition containing only the product name
228     if prod_info is None and os.path.isdir(version):
229         prod_info = src.pyconf.Config()
230         prod_info.name = product_name
231         prod_info.get_source = "fixed"
232         prod_info.addMapping("environ", src.pyconf.Mapping(prod_info), "")
233
234
235     # If prod_info is still None, it means that there is no product definition
236     # in the config. The user has to provide it.
237     if prod_info is None:
238         prod_pyconf_path = src.find_file_in_lpath(product_name + ".pyconf",
239                                                   config.PATHS.PRODUCTPATH)
240         if not prod_pyconf_path:
241             msg = _("""\
242 No definition found for the product %(1)s.
243 Please create a %(1)s.pyconf file somewhere in:
244   %(2)s""") % {
245   "1": product_name,
246   "2": PP.pformat(config.PATHS.PRODUCTPATH) }
247         else:
248             msg = _("""\
249 No definition corresponding to the version %(1)s was found in the file:
250   %(2)s.
251 Please add a section in it.""") % {"1" : vv, "2" : prod_pyconf_path}
252         raise src.SatException(msg)
253     
254     # Set the debug, dev and version keys
255     prod_info.debug = debug
256     prod_info.verbose = verbose
257     prod_info.dev = dev
258     prod_info.hpc = hpc
259     prod_info.version = version
260     if base != 'maybe':
261         prod_info.base = base
262
263     # Set the archive_info if the product is get in archive mode
264     if prod_info.get_source == "archive":
265         if not "archive_info" in prod_info:
266             prod_info.addMapping("archive_info",
267                                  src.pyconf.Mapping(prod_info),
268                                  "")
269         if "archive_name" in prod_info.archive_info: 
270             arch_name = prod_info.archive_info.archive_name
271         elif "archive_prefix" in prod_info.archive_info:
272             arch_name = prod_info.archive_info.archive_prefix + "-" + version + ".tar.gz"
273         else:
274             # standard name
275             arch_name = product_name + "-" + version + ".tar.gz"
276
277         arch_path = src.find_file_in_lpath(arch_name,
278                                            config.PATHS.ARCHIVEPATH)
279         if not arch_path:
280             # arch_path is not found. It may generate an error in sat source,
281             #                         unless the archive is found in ftp serveur
282             prod_info.archive_info.archive_name = arch_name #without path
283         else:
284             prod_info.archive_info.archive_name = arch_path
285
286         
287     # If the product compiles with a script, check the script existence
288     # and if it is executable
289     if product_has_script(prod_info):
290         # Check the compil_script key existence
291         if "compil_script" not in prod_info:
292             msg = _("""\
293 No compilation script found for the product %s.
294 Please provide a 'compil_script' key in its definition.""") % product_name
295             raise src.SatException(msg)
296         
297         # Get the path of the script file
298         # if windows supposed '.bat', if linux supposed '.sh'
299         # but user set extension script file in his pyconf as he wants, no obligation.
300         script = prod_info.compil_script
301         script_name = os.path.basename(script)
302         if script == script_name:
303             # Only a name is given. Search in the default directory
304             script_path = src.find_file_in_lpath(script_name, config.PATHS.PRODUCTPATH, "compil_scripts")
305             if not script_path:
306                 msg = _("Compilation script %s not found in") % script_name
307                 DBG.tofix(msg, config.PATHS.PRODUCTPATH, True) # say where searched
308                 script_path = "%s_(Not_Found_by_Sat!!)" % script_name
309             prod_info.compil_script = script_path
310
311        
312         # Check that the script is executable
313         if os.path.exists(prod_info.compil_script) and not os.access(prod_info.compil_script, os.X_OK):
314             DBG.tofix("Compilation script  file is not in 'execute mode'", prod_info.compil_script, True)
315     
316     # If the product has a post install script, check the script existence
317     # and if it is executable
318     if product_has_post_script(prod_info):
319         # Check the compil_script key existence
320         
321         # Get the path of the script file
322         # if windows supposed '.bat', if linux supposed '.sh'
323         # but user set extension script file in his pyconf as he wants, no obligation.
324         script = prod_info.post_script
325         script_name = os.path.basename(script)
326         if script == script_name:
327             # Only a name is given. Search in the default directory
328             script_path = src.find_file_in_lpath(script_name, config.PATHS.PRODUCTPATH, "post_scripts")
329             if not script_path:
330                 msg = _("Post install script %s not found in") % script_name
331                 DBG.tofix(msg, config.PATHS.PRODUCTPATH, True) # say where searched
332                 script_path = "%s_(Not_Found_by_Sat!!)" % script_name
333             prod_info.post_script = script_path
334
335        
336         # Check that the script is executable
337         if os.path.exists(prod_info.post_script) and not os.access(prod_info.post_script, os.X_OK):
338             DBG.tofix("Post install script file is not in 'execute mode'", prod_info.post_script, True)
339
340     # Get the full paths of all the patches
341     if product_has_patches(prod_info):
342         patches = []
343         try:
344           for patch in prod_info.patches:
345               patch_path = patch
346               # If only a filename, then search for the patch in the PRODUCTPATH
347               if os.path.basename(patch_path) == patch_path:
348                   # Search in the PRODUCTPATH/patches
349                   patch_path = src.find_file_in_lpath(patch,
350                                                       config.PATHS.PRODUCTPATH,
351                                                       "patches")
352                   if not patch_path:
353                       msg = _("Patch %(patch_name)s for %(prod_name)s not found:"
354                               "\n" % {"patch_name" : patch,
355                                        "prod_name" : prod_info.name}) 
356                       raise src.SatException(msg)
357               patches.append(patch_path)
358         except Exception:
359           DBG.tofix("problem in prod_info.patches", prod_info)
360         prod_info.patches = patches
361
362     # Get the full paths of the environment scripts
363     if product_has_env_script(prod_info):
364         env_script_path = prod_info.environ.env_script
365         # If only a filename, then search for the environment script 
366         # in the PRODUCTPATH/env_scripts
367         if os.path.basename(env_script_path) == env_script_path:
368             # Search in the PRODUCTPATH/env_scripts
369             env_script_path = src.find_file_in_lpath(
370                                             prod_info.environ.env_script,
371                                             config.PATHS.PRODUCTPATH,
372                                             "env_scripts")
373             if not env_script_path:
374                 msg = _("Environment script %(env_name)s for %(prod_name)s not "
375                         "found.\n" % {"env_name" : env_script_path,
376                                        "prod_name" : prod_info.name}) 
377                 raise src.SatException(msg)
378
379         prod_info.environ.env_script = env_script_path
380     
381     if with_install_dir: 
382         # The variable with_install_dir is at false only for internal use 
383         # of the function get_install_dir
384         
385         # Save the install_dir key if there is any
386         if "install_dir" in prod_info and not "install_dir_save" in prod_info:
387             prod_info.install_dir_save = prod_info.install_dir
388         
389         # if it is not the first time the install_dir is computed, it means
390         # that install_dir_save exists and it has to be taken into account.
391         if "install_dir_save" in prod_info:
392             prod_info.install_dir = prod_info.install_dir_save
393         
394         # Set the install_dir key
395         prod_info.install_dir,prod_info.install_mode = get_install_dir(config, version, prod_info)
396     return prod_info
397
398 def get_product_section(config, product_name, version, section=None):
399     """Build the product description from the configuration
400     
401     :param config Config: The global configuration
402     :param product_name str: The product name
403     :param version str: The version of the product as 'V8_4_0', or else.
404     :param section str: The searched section (if not None, the section is 
405                         explicitly given
406     :return: The product description
407     :rtype: Config
408     """
409
410
411     #get product definition and determine if the incremental definition mode is activated
412     aProd = config.PRODUCTS[product_name]
413     if "default" in aProd and\
414        "properties" in aProd.default and\
415        "incremental" in aProd.default.properties and\
416        aProd.default.properties.incremental == "yes":
417         # in this (new) mode the definition of the product is given by the default section
418         # and is incremented by others.
419         is_incr=True
420     else:
421         # in this (historic) mode the definition of the product is given by a full unique section
422         is_incr=False
423
424     # decode version number
425     try:
426       versionMMP = VMMP.MinorMajorPatch(version)
427     except Exception: # example setuptools raise "minor in major_minor_patch is not integer: '0_6c11'"
428       versionMMP = None
429
430     # if a section is explicitely specified we select it
431     if section:
432         if section not in aProd:
433             pi=None
434         # returns specific information for the given version
435         pi = aProd[section]
436         pi.section = section
437         pi.from_file = aProd.from_file
438
439     # If it exists, get the information of the product_version
440     # ex: 'version_V6_6_0' as salome version classical syntax
441     elif "version_" + version in aProd:
442         # returns specific information for the given version
443         pi = aProd["version_" + version]
444         pi.section = "version_" + version
445         pi.from_file = aProd.from_file
446
447     # Else, check if there is a description for multiple versions
448     else:
449         l_section_names = aProd.keys()
450         l_section_ranges = []
451         tagged = []
452         for name in l_section_names:
453           aRange = VMMP.getRange_majorMinorPatch(name)
454           if aRange is not None:
455             l_section_ranges.append((name, aRange))
456
457         if versionMMP is not None and len(l_section_ranges) > 0:
458           for name, (vmin, vmax) in l_section_ranges:
459             if versionMMP >= vmin and versionMMP <= vmax:
460               tagged.append((name, [vmin, vmax]))
461
462         if len(tagged) > 1:
463           DBG.write("multiple version ranges tagged for '%s', fix it" % version,
464                          PP.pformat(tagged))
465           pi=None
466         elif len(tagged) == 1: # ok
467           name, (vmin, vmax) = tagged[0]
468           pi = aProd[name]
469           pi.section = name
470           pi.from_file = aProd.from_file
471
472         # Else, get the standard informations
473         elif "default" in aProd:
474             # returns the generic information (given version not found)
475             pi = aProd.default
476             pi.section = "default"
477             pi.from_file = aProd.from_file
478         else:
479             pi=None
480
481     if is_incr:
482         # If the definition is incremental, we take the default section
483         # and then complete it with other sections : 
484         #   - default_win
485         #   - the selected section (pi)
486         #   - the selected _win section
487         prod_info=aProd["default"]
488         prod_info.from_file = aProd.from_file
489         prod_info.section = "default"
490         if src.architecture.is_windows() and "default_win" in aProd:
491             for key in aProd["default_win"]:
492                 prod_info[key]=aProd["default_win"][key]
493         if pi!=None and pi.section!="default":
494             # update prod_info with incremental definition contained in pi
495             for key in pi:
496                 prod_info[key]=pi[key]
497             win_section=pi.section+"_win"
498             if src.architecture.is_windows() and win_section in aProd:
499                 for key in aProd[win_section]:
500                     prod_info[key]=aProd[win_section][key]
501     else:
502         prod_info=pi
503
504     #DBG.write("product info returned for product %s with version %s and section %s" %\
505     #          (product_name, version, section), prod_info)
506     return prod_info
507     
508 def get_install_dir(config, version, prod_info):
509     """Compute the installation directory of a given product 
510     
511     :param config Config: The global configuration
512     :param base str: This corresponds to the value given by user in its 
513                      application.pyconf for the specific product. If "yes", the
514                      user wants the product to be in base. If "no", he wants the
515                      product to be in the application workdir
516     :param version str: The version of the product
517     :param product_info Config: The configuration specific to 
518                                the product
519     
520     :return: The path of the product installation and the mode of the install directory (base/implicit/fixed/value)
521     :rtype: str,str
522     """
523     install_dir = ""
524     in_base = False
525     
526     # prod_info.base : corresponds to what is specified in application pyconf (either from the global key base, or from a product dict)
527     # prod_info.install_dir : corresponds to what is specified in product pyconf (usually "base" for prerequisites)
528     if ( ("install_dir" in prod_info and prod_info.install_dir == "base") # product is declared as base in its own config 
529                                       or ("base" in prod_info  and prod_info.base != "no") ):  # product is declared as base in the application
530         # a product goes in base if install_dir is set to base, or if product was declared based in application pyconf
531         in_base = True
532
533     # check desactivation of base at application level
534     if ( "base" in prod_info  and prod_info.base == "no"):
535         in_base = False
536
537     if in_base:
538         install_dir = get_base_install_dir(config, prod_info, version)
539         install_mode = "base"
540     else:
541         if ("install_mode" in prod_info and prod_info.install_mode in ["implicit", "base"]) or\
542            ("install_dir" not in prod_info or prod_info.install_dir == "base"):
543             # the check to "base" comes from the package case were base mode is changed dynamically 
544             # to create a package launcher.
545
546             # Set it to the default value (in application directory)
547             install_mode = "implicit"
548             if ( src.appli_test_property(config,"single_install_dir", "yes") and 
549                  src.product.product_test_property(prod_info,"single_install_dir", "yes")):
550                 # when single_install_dir mode is activated in tha application
551                 # we use config.INTERNAL.config.single_install_dir for products 
552                 # having single_install_dir property
553                 install_dir = os.path.join(config.APPLICATION.workdir,
554                                            config.INTERNAL.config.install_dir,
555                                            config.INTERNAL.config.single_install_dir)
556             elif ( src.appli_test_property(config,"pip", "yes") and 
557                    src.product.product_test_property(prod_info,"pip", "yes") and
558                    src.appli_test_property(config,"pip_install_dir", "python") ):
559                 # when pip mode is activated in the application
560                 # and product is pip, and pip_install_dir is set to python 
561                 # we assume python in installed in install_dir/Python
562                 install_dir = os.path.join(config.APPLICATION.workdir,
563                                            config.INTERNAL.config.install_dir,
564                                            "Python")   
565             else:
566                 install_dir = os.path.join(config.APPLICATION.workdir,
567                                            config.INTERNAL.config.install_dir,
568                                            prod_info.name)
569         else:
570             install_dir = prod_info.install_dir
571             install_mode = "value"
572
573     return install_dir,install_mode
574
575 def get_base_install_dir(config, prod_info, version):
576     """Compute the installation directory of a product in base 
577     
578     :param config Config: The global configuration
579     :param product_info Config: The configuration specific to 
580                                the product
581     :param version str: The version of the product    
582     :param base str: This corresponds to the value given by user in its 
583                      application.pyconf for the specific product. If "yes", the
584                      user wants the product to be in base. If "no", he wants the
585                      product to be in the application workdir.
586                      if it contains something else, is is interpreted as the name 
587                      of a base we build for module load.
588     :return: The path of the product installation
589     :rtype: str
590     """    
591     
592     # get rid of / to avoid create subdirectories cf sat #18546
593     version_wslash=version.replace("/", "_") 
594
595     if ( src.appli_test_property(config,"pip", "yes") and 
596          src.product.product_test_property(prod_info,"pip", "yes") and
597          src.appli_test_property(config,"pip_install_dir", "python") ):
598          # when pip mode is activated in the application
599          # and product is pip, and pip_install_dir is set to python 
600         python_info=get_product_config(config, "Python")
601         return python_info.install_dir
602
603     base_path = src.get_base_path(config) 
604     if "base" in prod_info and prod_info.base != "no" and prod_info.base != "yes":
605         # we are in the case of a named base
606         prod_dir = os.path.join(base_path, "apps", prod_info.base, prod_info.name, version_wslash)
607         return prod_dir
608     
609     prod_dir = os.path.join(base_path, prod_info.name + "-" + version_wslash)
610     if not os.path.exists(prod_dir):
611         return os.path.join(prod_dir, "config-1")
612     
613     exists, install_dir = check_config_exists(config, prod_dir, prod_info)
614     if exists:
615         return install_dir
616     
617     # Find the first config-<i> directory that is available in the product
618     # directory
619     found = False 
620     label = 1
621     while not found:
622         install_dir = os.path.join(prod_dir, "config-%i" % label)
623         if os.path.exists(install_dir):
624             label+=1
625         else:
626             found = True
627             
628     return install_dir
629
630 def add_compile_config_file(p_info, config):
631     '''Execute the proper configuration command(s)
632        in the product build directory.
633
634     :param p_info Config: The specific config of the product
635     :param config Config: The global configuration
636     '''
637     # Create the compile config
638     # DBG.write("add_compile_config_file", p_info, True)
639     res = src.pyconf.Config()
640     res.addMapping(p_info.name, src.pyconf.Mapping(res), "")
641     res[p_info.name]= p_info.version
642
643     depprod=[]
644     for d in p_info.depend:
645         depprod.append(d)
646     if "build_depend" in p_info:
647         for d in p_info.build_depend:
648             depprod.append(d)
649     for prod_name in depprod:
650       if prod_name not in res:
651         res.addMapping(prod_name, src.pyconf.Mapping(res), "")
652       prod_dep_info = src.product.get_product_config(config, prod_name, False)
653       res[prod_name] = prod_dep_info.version
654     # Write it in the install directory of the product
655     # This file is for automatic reading/checking
656     # see check_config_exists method
657     afilename = CONFIG_FILENAME + p_info.name + ".pyconf"
658     aFile = os.path.join(p_info.install_dir, afilename)
659     with open(aFile, 'w') as f:
660       res.__save__(f)
661
662     # this file is not mandatory, is for human eye reading
663     afilename = PRODUCT_FILENAME + p_info.name + ".pyconf"
664     aFile = os.path.join(p_info.install_dir, afilename)
665     try:
666       with open(aFile, 'w') as f:
667         p_info.__save__(f, evaluated=True) # evaluated expressions mode
668     except Exception:
669       # sometime some information cannot be evaluated.
670       # for example, in the context of non VCS archives, information on git server is not available.
671       DBG.write("Warning : sat was not able to evaluate and write down some information in file %s" % aFile)
672   
673
674 def check_config_exists(config, prod_dir, prod_info, verbose=False):
675     """\
676     Verify that the installation directory of a product in a base exists.
677     Check all the config-<i>/sat-config.py files found for correspondence
678     with current config and prod_info depend-version-tags
679     
680     :param config Config: The global configuration
681     :param prod_dir str: The product installation directory path 
682                          (without config-<i>)
683     :param product_info Config: The configuration specific to 
684                                the product
685     :return: True or false is the installation is found or not 
686              and if it is found, the path of the found installation
687     :rtype: (boolean, str)
688     """
689     # check if the directories or files of the directory corresponds to the
690     # directory installation of the product
691     if os.path.isdir(prod_dir):
692       l_dir_and_files = os.listdir(prod_dir)
693     else:
694       raise Exception("Inexisting directory '%s'" % prod_dir)
695
696     DBG.write("check_config_exists 000",  (prod_dir, l_dir_and_files), verbose)
697     DBG.write("check_config_exists 111",  prod_info, verbose)
698
699     depend_all=[]
700     if "depend" in prod_info:
701         for d in prod_info.depend:
702             depend_all.append(d)
703     if "build_depend" in prod_info:
704         for d in prod_info.build_depend:
705             depend_all.append(d)
706     for dir_or_file in l_dir_and_files:
707         oExpr = re.compile(config_expression)
708         if not(oExpr.search(dir_or_file)):
709             # in mode BASE, not config-<i>, not interesting
710             # DBG.write("not interesting", dir_or_file, True)
711             continue
712         # check if there is the file sat-config.pyconf file in the installation
713         # directory    
714         afilename = CONFIG_FILENAME + prod_info.name + ".pyconf"
715         config_file = os.path.join(prod_dir, dir_or_file, afilename)
716         DBG.write("check_config_exists 222", config_file, verbose)
717         if not os.path.exists(config_file):
718             continue
719         
720         # check if there is the config described in the file corresponds the 
721         # dependencies of the product
722         config_corresponds = True    
723         compile_cfg = src.pyconf.Config(config_file)
724         for prod_dep in depend_all:
725             # if the dependency is not in the config, 
726             # the config does not correspond
727             if prod_dep not in compile_cfg:
728                 config_corresponds = False
729                 break
730             else:
731                 prod_dep_info = get_product_config(config, prod_dep, False)
732                 # If the version of the dependency does not correspond, 
733                 # the config does not correspond
734                 if prod_dep_info.version != compile_cfg[prod_dep]:
735                     config_corresponds = False
736                     break
737
738         if config_corresponds:
739           for prod_name in compile_cfg:
740             # assume new compatibility with prod_name in sat-config.pyconf files
741             if prod_name == prod_info.name:
742               if prod_info.version == compile_cfg[prod_name]:
743                 DBG.write("check_config_exists OK 333", compile_cfg, verbose)
744                 pass
745               else: # no correspondence with newer with prod_name sat-config.pyconf files
746                 config_corresponds = False
747                 break
748             else:
749               # as old compatibility without prod_name sat-config.pyconf files
750               if prod_name not in depend_all:
751                 # here there is an unexpected depend in an old compilation
752                 config_corresponds = False
753                 break
754         
755         if config_corresponds: # returns (and stops) at first correspondence found
756             DBG.write("check_config_exists OK 444", dir_or_file, verbose)
757             return True, os.path.join(prod_dir, dir_or_file)
758
759     # no correspondence found
760     return False, None
761             
762             
763     
764 def get_products_infos(lproducts, config):
765     """Get the specific configuration of a list of products
766     
767     :param lproducts List: The list of product names
768     :param config Config: The global configuration
769     :return: the list of tuples 
770              (product name, specific configuration of the product)
771     :rtype: [(str, Config)]
772     """
773     products_infos = []
774     # Loop on product names
775     for prod in lproducts:       
776         # Get the specific configuration of the product
777         prod_info = get_product_config(config, prod)
778         if prod_info is not None:
779             products_infos.append((prod, prod_info))
780         else:
781             msg = _("The %s product has no definition in the configuration.") % prod
782             raise src.SatException(msg)
783     return products_infos
784
785
786 def get_products_list(options, cfg, logger):
787     """
788     method that gives the product list with their informations from
789     configuration regarding the passed options.
790
791     :param options Options: The Options instance that stores the commands arguments
792     :param cfg Config: The global configuration
793     :param logger Logger: The logger instance to use for the display and logging
794     :return: The list of (product name, product_informations).
795     :rtype: List
796     """
797     # Get the products to be prepared, regarding the options
798     if options.products is None:
799         # No options, get all products sources
800         products=[]
801         for product in cfg.APPLICATION.products.keys():
802             prod_info = get_product_config(cfg, product)
803             if prod_info is None:
804                 logger.error("%s does not have associated information" % (product))
805                 continue
806             if 'get_source' in prod_info and prod_info.get_source == 'git':
807                 git_server = src.get_git_server(cfg,logger)
808             else:
809                 git_server =  cfg.VARS['default_git_server_dev']
810
811             if src.product.product_is_not_opensource(prod_info) and not src.git_server_has_all_repositories(cfg, git_server):
812                 logger.warning("%s is a closed-source software and is not available on %s" % (product, git_server))
813                 logger.flush()
814                 continue
815             products+=[product]
816         products = src.getProductNames(cfg, products, logger)
817     else:
818         # if option --products, check that all products of the command line
819         # are present in the application.
820         """products = options.products
821         for p in products:
822             if p not in cfg.APPLICATION.products:
823                 raise src.SatException(_("Product %(product)s "
824                             "not defined in application %(application)s") %
825                         { 'product': p, 'application': cfg.VARS.application} )"""
826
827         products = src.getProductNames(cfg, options.products, logger)
828     # Construct the list of tuple containing
829     # the products name and their definition
830     resAll = src.product.get_products_infos(products, cfg)
831
832     # if the property option was passed, filter the list
833     if options.properties: # existing properties
834       ok = []
835       ko = []
836       res =[]
837       prop, value = options.properties # for example 'is_SALOME_module', 'yes'
838       if value[0] == '!':
839           for p_name, p_info in resAll:
840             try:
841               if p_info.properties[prop] == value[1:]:
842                 ko.append(p_name)
843               else:
844                 res.append((p_name, p_info))
845                 ok.append(p_name)
846             except Exception:
847               res.append((p_name, p_info))
848               ok.append(p_name)
849       else:
850           for p_name, p_info in resAll:
851             try:
852               if p_info.properties[prop] == value:
853                 res.append((p_name, p_info))
854                 ok.append(p_name)
855               else:
856                 ko.append(p_name)
857             except Exception:
858               ko.append(p_name)
859
860       if len(ok) != len(resAll):
861         logger.trace("on properties %s\n products accepted:\n %s\n products rejected:\n %s\n" %
862                        (options.properties, PP.pformat(sorted(ok)), PP.pformat(sorted(ko))))
863       else:
864         logger.warning("properties %s\n seems useless with no products rejected" %
865                        (options.properties))
866     else:
867       res = resAll # not existing properties as all accepted
868
869     return res
870
871
872 def get_product_dependencies(config, product_name, product_info):
873     """\
874     Get the list of products that are 
875     in the product_info dependencies
876     
877     :param config Config: The global configuration
878     :param product_info Config: The configuration specific to 
879                                the product
880     :return: the list of products in dependence
881     :rtype: list
882     """
883     from compile import get_dependencies_graph, depth_search_graph
884     all_products_infos = get_products_infos(
885                              config.APPLICATION.products,
886                              config)
887     all_products_graph=get_dependencies_graph(all_products_infos)
888     res=[]
889     res=depth_search_graph(all_products_graph, product_name, res)
890     return res[1:]  # remove the product himself (in first position)
891
892 def check_installation(config, product_info):
893     """\
894     Verify if a product is well installed. Checks install directory presence
895     and some additional files if it is defined in the config 
896     
897     :param product_info Config: The configuration specific to 
898                                the product
899     :return: True if it is well installed
900     :rtype: boolean
901     """
902     # don't check products that are not compiled
903     if not product_compiles(product_info):
904         return True
905
906     if product_is_native(product_info):
907         # check a system product
908         check_cmd=src.system.get_pkg_check_cmd(config.VARS.dist_name)
909         run_pkg,build_pkg=src.product.check_system_dep(config.VARS.dist, check_cmd, product_info)
910         build_dep_ko=[]
911         for pkg in build_pkg:
912             if "KO" in build_pkg[pkg]:
913                build_dep_ko.append(pkg) 
914         if build_dep_ko:
915               # the product is not installed : display message and return error status
916               msg="Please install them with %s before compiling salome" % check_cmd[0]
917               print("\nmissing compile time dependencies : ")
918               for md in build_dep_ko: 
919                   print(md)
920               print(msg)
921               return False
922         else:
923             return True    
924
925     install_dir = product_info.install_dir
926     if src.product.product_is_fixed(product_info):
927         # we check directly the install dir only for fixed products
928         # (there is no pyconf file in that case)
929         if not os.path.exists(install_dir):
930             return False
931     else:
932         filename = CONFIG_FILENAME + product_info.name + ".pyconf"
933         if not os.path.exists(os.path.join(install_dir, filename)): 
934             return False
935
936     # check extra files if specified in present_files.install section
937     if ("present_files" in product_info and 
938         "install" in product_info.present_files):
939         for file_relative_path in product_info.present_files.install:
940             file_path = os.path.join(install_dir, file_relative_path)
941             if not os.path.exists(file_path):
942                 return False
943     return True
944
945 def check_source(product_info):
946     """Verify if a sources of product is preset. Checks source directory presence
947     
948     :param product_info Config: The configuration specific to 
949                                the product
950     :return: True if it is well installed
951     :rtype: boolean
952     """
953     source_dir = product_info.source_dir
954     if not os.path.exists(source_dir):
955         return False
956     if ("present_files" in product_info and 
957         "source" in product_info.present_files):
958         for file_relative_path in product_info.present_files.source:
959             file_path = os.path.join(source_dir, file_relative_path)
960             if not os.path.exists(file_path):
961                 return False
962     return True
963
964 def product_is_salome(product_info):
965     """Know if a product is a SALOME module
966     
967     :param product_info Config: The configuration specific to 
968                                the product
969     :return: True if the product is a SALOME module, else False
970     :rtype: boolean
971     """
972     return ("properties" in product_info and
973             "is_SALOME_module" in product_info.properties and
974             product_info.properties.is_SALOME_module == "yes")
975
976 def product_is_configuration(product_info):
977     """Know if a product is a configuration module
978     
979     :param product_info Config: The configuration specific to 
980                                the product
981     :return: True if the product is a configuration module, else False
982     :rtype: boolean
983     """
984     return ("properties" in product_info and
985             "configure_dependency" in product_info.properties and
986             product_info.properties.configure_dependency == "yes")
987
988 def product_is_fixed(product_info):
989     """Know if a product is fixed
990     
991     :param product_info Config: The configuration specific to 
992                                the product
993     :return: True if the product is fixed, else False
994     :rtype: boolean
995     """
996     get_src = product_info.get_source
997     return get_src.lower() == 'fixed'
998
999 def product_is_native(product_info):
1000     """Know if a product is native
1001     
1002     :param product_info Config: The configuration specific to 
1003                                the product
1004     :return: True if the product is native, else False
1005     :rtype: boolean
1006     """
1007     get_src = product_info.get_source
1008     return get_src.lower() == 'native'
1009
1010 def product_is_dev(product_info):
1011     """Know if a product is in dev mode
1012     
1013     :param product_info Config: The configuration specific to 
1014                                the product
1015     :return: True if the product is in dev mode, else False
1016     :rtype: boolean
1017     """
1018     dev = product_info.dev
1019     res = (dev.lower() == 'yes')
1020     DBG.write('product_is_dev %s' % product_info.name, res)
1021     # if product_info.name == "XDATA": return True #test #10569
1022     return res
1023
1024 def product_is_hpc(product_info):
1025     """Know if a product is in hpc mode
1026     
1027     :param product_info Config: The configuration specific to 
1028                                the product
1029     :return: True if the product is in hpc mode, else False
1030     :rtype: boolean
1031     """
1032     hpc = product_info.hpc
1033     res = (hpc.lower() == 'yes')
1034     return res
1035
1036 def product_is_debug(product_info):
1037     """Know if a product is in debug mode
1038     
1039     :param product_info Config: The configuration specific to 
1040                                the product
1041     :return: True if the product is in debug mode, else False
1042     :rtype: boolean
1043     """
1044     debug = product_info.debug
1045     return debug.lower() == 'yes'
1046
1047 def product_is_verbose(product_info):
1048     """Know if a product is in verbose mode
1049     
1050     :param product_info Config: The configuration specific to 
1051                                the product
1052     :return: True if the product is in verbose mode, else False
1053     :rtype: boolean
1054     """
1055     verbose = product_info.verbose
1056     return verbose.lower() == 'yes'
1057
1058 def product_is_autotools(product_info):
1059     """Know if a product is compiled using the autotools
1060     
1061     :param product_info Config: The configuration specific to 
1062                                the product
1063     :return: True if the product is autotools, else False
1064     :rtype: boolean
1065     """
1066     build_src = product_info.build_source
1067     return build_src.lower() == 'autotools'
1068
1069 def product_is_cmake(product_info):
1070     """Know if a product is compiled using the cmake
1071     
1072     :param product_info Config: The configuration specific to 
1073                                the product
1074     :return: True if the product is cmake, else False
1075     :rtype: boolean
1076     """
1077     build_src = product_info.build_source
1078     return build_src.lower() == 'cmake'
1079
1080 def product_is_vcs(product_info):
1081     """Know if a product is download using git, svn or cvs (not archive)
1082     
1083     :param product_info Config: The configuration specific to 
1084                                the product
1085     :return: True if the product is vcs, else False
1086     :rtype: boolean
1087     """
1088     return product_info.get_source in AVAILABLE_VCS
1089
1090 def product_is_smesh_plugin(product_info):
1091     """Know if a product is a SMESH plugin
1092     
1093     :param product_info Config: The configuration specific to 
1094                                the product
1095     :return: True if the product is a SMESH plugin, else False
1096     :rtype: boolean
1097     """
1098     return ("properties" in product_info and
1099             "smesh_plugin" in product_info.properties and
1100             product_info.properties.smesh_plugin == "yes")
1101
1102 def product_is_cpp(product_info):
1103     """Know if a product is cpp
1104     
1105     :param product_info Config: The configuration specific to 
1106                                the product
1107     :return: True if the product is a cpp, else False
1108     :rtype: boolean
1109     """
1110     return ("properties" in product_info and
1111             "cpp" in product_info.properties and
1112             product_info.properties.cpp == "yes")
1113
1114 def product_is_not_opensource(product_info):
1115     """Check if a given product is closed-source
1116     
1117     :param product_info Config: The configuration specific to 
1118                                the product
1119     :return: True if the product is a closed-source, False otherwise
1120     :rtype: boolean
1121     """
1122     return ("properties" in product_info and
1123             "is_opensource" in product_info.properties and
1124             product_info.properties.is_opensource == "no")
1125
1126 def product_compiles(product_info):
1127     """\
1128     Know if a product compiles or not 
1129     (some products do not have a compilation procedure)
1130     
1131     :param product_info Config: The configuration specific to 
1132                                the product
1133     :return: True if the product compiles, else False
1134     :rtype: boolean
1135     """
1136     return not("properties" in product_info and
1137             "compilation" in product_info.properties and
1138             product_info.properties.compilation == "no")
1139
1140 def product_has_script(product_info):
1141     """Know if a product has a compilation script
1142     
1143     :param product_info Config: The configuration specific to 
1144                                the product
1145     :return: True if the product it has a compilation script, else False
1146     :rtype: boolean
1147     """
1148     if "build_source" not in product_info:
1149         # Native case
1150         return False
1151     build_src = product_info.build_source
1152     return build_src.lower() == 'script'
1153
1154 def product_has_env_script(product_info):
1155     """Know if a product has an environment script
1156     
1157     :param product_info Config: The configuration specific to 
1158                                the product
1159     :return: True if the product it has an environment script, else False
1160     :rtype: boolean
1161     """
1162     return "environ" in product_info and "env_script" in product_info.environ
1163
1164 def product_has_patches(product_info):
1165     """Know if a product has one or more patches
1166     
1167     :param product_info Config: The configuration specific to 
1168                                the product
1169     :return: True if the product has one or more patches
1170     :rtype: boolean
1171     """   
1172     res = ( "patches" in product_info and len(product_info.patches) > 0 )
1173     return res
1174
1175 def product_has_post_script(product_info):
1176     """Know if a product has a post install script
1177     
1178     :param product_info Config: The configuration specific to 
1179                                the product
1180     :return: True if the product has one or more patches
1181     :rtype: boolean
1182     """   
1183     res = ( "post_script" in product_info and len(product_info.post_script) > 0 and not src.architecture.is_windows())
1184     return res
1185
1186 def product_has_logo(product_info):
1187     """Know if a product has a logo (YACSGEN generate)
1188     
1189     :param product_info Config: The configuration specific to 
1190                                the product
1191     :return: The path of the logo if the product has a logo, else False
1192     :rtype: Str
1193     """
1194     if ("properties" in product_info and
1195             "logo" in product_info.properties):
1196         return product_info.properties.logo
1197     else:
1198         return False
1199
1200 def product_has_licence(product_info, path):
1201     """Find out if a product has a licence
1202     
1203     :param product_info Config: The configuration specific to the product
1204     :param path Str: The path where to search for the licence
1205     :return: The name of the licence file (the complete path if it is found in the path, else the name, else False
1206     :rtype: Str
1207     """
1208     if ("properties" in product_info and
1209             "licence" in product_info.properties):
1210         licence_name = product_info.properties.licence
1211         if len(path) > 0:
1212             # search for licence_name in path
1213             # a- consolidate the path into one signe string licence_path
1214             licence_path=path[0]
1215             for lpath in path[1:]:
1216                 licence_path=licence_path+":"+lpath
1217             licence_path_list=licence_path.split(":")
1218             licence_fullname = src.find_file_in_lpath(licence_name, licence_path_list)
1219             if licence_fullname:
1220                 return licence_fullname
1221
1222         # if the search of licence in path failed, we return its name (not the full path) 
1223         return licence_name
1224
1225     else:
1226         return False  # product has no licence
1227
1228 def product_has_salome_gui(product_info):
1229     """Know if a product has a SALOME gui
1230     
1231     :param product_info Config: The configuration specific to 
1232                                the product
1233     :return: True if the product has a SALOME gui, else False
1234     :rtype: Boolean
1235     """
1236     return ("properties" in product_info and
1237             "has_salome_gui" in product_info.properties and
1238             product_info.properties.has_salome_gui == "yes")
1239
1240 def product_is_mpi(product_info):
1241     """Know if a product has openmpi in its dependencies
1242     
1243     :param product_info Config: The configuration specific to 
1244                                the product
1245     :return: True if the product has openmpi inits dependencies
1246     :rtype: boolean
1247     """
1248     return "openmpi" in product_info.depend
1249
1250 def product_is_generated(product_info):
1251     """Know if a product is generated (YACSGEN)
1252     
1253     :param product_info Config: The configuration specific to 
1254                                the product
1255     :return: True if the product is generated
1256     :rtype: boolean
1257     """
1258     return ("properties" in product_info and
1259             "generate" in product_info.properties and
1260             product_info.properties.generate == "yes")
1261
1262 def product_is_compile_time(product_info):
1263     """Know if a product is only used at compile time
1264     
1265     :param product_info Config: The configuration specific to 
1266                                the product
1267     :return: True if the product is only used at compile time
1268     :rtype: boolean
1269     """
1270     return ("properties" in product_info and
1271             "compile_time" in product_info.properties and
1272             product_info.properties.compile_time == "yes")
1273
1274 def product_is_compile_and_runtime(product_info):
1275     """Know if a product is only used at compile time
1276     
1277     :param product_info Config: The configuration specific to 
1278                                the product
1279     :return: True if the product is only used at compile time
1280     :rtype: boolean
1281     """
1282     return ("properties" in product_info and
1283             "compile_and_runtime" in product_info.properties and
1284             product_info.properties.compile_and_runtime == "yes")
1285
1286
1287
1288 def product_test_property(product_info, property_name, property_value):
1289     """Generic function to test if a product has a property set to a value
1290     
1291     :param product_info Config: The configuration specific to 
1292                                the product
1293     :param property_name : The name of the property to check
1294     :param property_value : The value of the property to test
1295     :return: True if the product has the property and the property is set to property_value
1296     :rtype: boolean
1297     """
1298     # first check if product has the property
1299     if not ("properties" in product_info and
1300             property_name in product_info.properties):
1301         return False
1302   
1303     # then check to the property is set to property_value
1304     eval_expression = 'product_info.properties.%s == "%s"' % (property_name,property_value)
1305     result = eval(eval_expression)
1306     return result
1307
1308 def check_system_dep(distrib, check_cmd, product_info):
1309     """Search for system dependencies, check if installed
1310     :param dist : The linux ditribution (CO7,DB10...)
1311     :param check_cmd Config: The command to use for checking (rpm/apt)
1312     :param product_info Config: The configuration specific to the product
1313     :rtype: two dictionnaries for runtime and compile time dependencies with text status
1314     """
1315     runtime_dep={}
1316     build_dep={}
1317
1318     if "system_info" in product_info:
1319
1320         sysinfo=product_info.system_info
1321         additional_sysinfo = None
1322
1323         for key in sysinfo :
1324             if distrib in key :
1325                 additional_sysinfo = sysinfo[key]
1326
1327         if check_cmd[0]=="rpm":
1328             if "rpm" in sysinfo:
1329                 for pkg in sysinfo.rpm:
1330                     runtime_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1331             if "rpm_dev" in sysinfo:
1332                 for pkg in sysinfo.rpm_dev:
1333                     build_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1334             if additional_sysinfo :
1335                 if "rpm" in additional_sysinfo:
1336                     for pkg in additional_sysinfo.rpm:
1337                         runtime_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1338                 if "rpm_dev" in additional_sysinfo:
1339                     for pkg in additional_sysinfo.rpm_dev:
1340                         build_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1341         if check_cmd[0]=="apt" or check_cmd[0]=="dpkg-query":
1342             if "apt" in sysinfo:
1343                 for pkg in sysinfo.apt:
1344                     runtime_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1345             if "apt_dev" in sysinfo:
1346                 for pkg in sysinfo.apt_dev:
1347                     build_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1348             if additional_sysinfo :
1349                 if "apt" in additional_sysinfo:
1350                     for pkg in additional_sysinfo.apt:
1351                         runtime_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1352                 if "apt_dev" in additional_sysinfo:
1353                     for pkg in additional_sysinfo.apt_dev:
1354                         build_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1355
1356     return runtime_dep,build_dep
1357
1358
1359 def get_product_components(product_info):
1360     """Get the component list to generate with the product
1361     
1362     :param product_info Config: The configuration specific to 
1363                                the product
1364     :return: The list of names of the components
1365     :rtype: List
1366     
1367     """
1368     if not product_is_generated(product_info):
1369         return []
1370     
1371     compo_list = []
1372     if "component_name" in product_info:
1373         compo_list = product_info.component_name
1374     
1375         if isinstance(compo_list, str):
1376             compo_list = [ compo_list ]
1377
1378     return compo_list
1379 def product_is_wheel(product_info):
1380     """ tells whether a product is a wheel
1381     
1382     :param product_info Config: The configuration specific to 
1383                                the product
1384     :return: True if the product has a wheel, else False
1385     :rtype: Boolean
1386     """
1387     return ("properties" in product_info and
1388             "is_wheel" in product_info.properties and
1389             product_info.properties.is_wheel == "yes")
1390