Salome HOME
spns #34338: post build script is not embedded in archive
[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:
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                 
397     return prod_info
398
399 def get_product_section(config, product_name, version, section=None):
400     """Build the product description from the configuration
401     
402     :param config Config: The global configuration
403     :param product_name str: The product name
404     :param version str: The version of the product as 'V8_4_0', or else.
405     :param section str: The searched section (if not None, the section is 
406                         explicitly given
407     :return: The product description
408     :rtype: Config
409     """
410
411
412     #get product definition and determine if the incremental definition mode is activated
413     aProd = config.PRODUCTS[product_name]
414     if "default" in aProd and\
415        "properties" in aProd.default and\
416        "incremental" in aProd.default.properties and\
417        aProd.default.properties.incremental == "yes":
418         # in this (new) mode the definition of the product is given by the default section
419         # and is incremented by others.
420         is_incr=True
421     else:
422         # in this (historic) mode the definition of the product is given by a full unique section
423         is_incr=False
424
425     # decode version number
426     try:
427       versionMMP = VMMP.MinorMajorPatch(version)
428     except: # example setuptools raise "minor in major_minor_patch is not integer: '0_6c11'"
429       versionMMP = None
430
431     # if a section is explicitely specified we select it
432     if section:
433         if section not in aProd:
434             pi=None
435         # returns specific information for the given version
436         pi = aProd[section]
437         pi.section = section
438         pi.from_file = aProd.from_file
439
440     # If it exists, get the information of the product_version
441     # ex: 'version_V6_6_0' as salome version classical syntax
442     elif "version_" + version in aProd:
443         # returns specific information for the given version
444         pi = aProd["version_" + version]
445         pi.section = "version_" + version
446         pi.from_file = aProd.from_file
447
448     # Else, check if there is a description for multiple versions
449     else:
450         l_section_names = aProd.keys()
451         l_section_ranges = []
452         tagged = []
453         for name in l_section_names:
454           aRange = VMMP.getRange_majorMinorPatch(name)
455           if aRange is not None:
456             l_section_ranges.append((name, aRange))
457
458         if versionMMP is not None and len(l_section_ranges) > 0:
459           for name, (vmin, vmax) in l_section_ranges:
460             if versionMMP >= vmin and versionMMP <= vmax:
461               tagged.append((name, [vmin, vmax]))
462
463         if len(tagged) > 1:
464           DBG.write("multiple version ranges tagged for '%s', fix it" % version,
465                          PP.pformat(tagged))
466           pi=None
467         elif len(tagged) == 1: # ok
468           name, (vmin, vmax) = tagged[0]
469           pi = aProd[name]
470           pi.section = name
471           pi.from_file = aProd.from_file
472
473         # Else, get the standard informations
474         elif "default" in aProd:
475             # returns the generic information (given version not found)
476             pi = aProd.default
477             pi.section = "default"
478             pi.from_file = aProd.from_file
479         else:
480             pi=None
481
482     if is_incr:
483         # If the definition is incremental, we take the default section
484         # and then complete it with other sections : 
485         #   - default_win
486         #   - the selected section (pi)
487         #   - the selected _win section
488         prod_info=aProd["default"]
489         prod_info.from_file = aProd.from_file
490         prod_info.section = "default"
491         if src.architecture.is_windows() and "default_win" in aProd:
492             for key in aProd["default_win"]:
493                 prod_info[key]=aProd["default_win"][key]
494         if pi!=None and pi.section!="default":
495             # update prod_info with incremental definition contained in pi
496             for key in pi:
497                 prod_info[key]=pi[key]
498             win_section=pi.section+"_win"
499             if src.architecture.is_windows() and win_section in aProd:
500                 for key in aProd[win_section]:
501                     prod_info[key]=aProd[win_section][key]
502     else:
503         prod_info=pi
504
505     #DBG.write("product info returned for product %s with version %s and section %s" %\
506     #          (product_name, version, section), prod_info)
507     return prod_info
508     
509 def get_install_dir(config, version, prod_info):
510     """Compute the installation directory of a given product 
511     
512     :param config Config: The global configuration
513     :param base str: This corresponds to the value given by user in its 
514                      application.pyconf for the specific product. If "yes", the
515                      user wants the product to be in base. If "no", he wants the
516                      product to be in the application workdir
517     :param version str: The version of the product
518     :param product_info Config: The configuration specific to 
519                                the product
520     
521     :return: The path of the product installation and the mode of the install directory (base/implicit/fixed/value)
522     :rtype: str,str
523     """
524     install_dir = ""
525     in_base = False
526     
527     # prod_info.base : corresponds to what is specified in application pyconf (either from the global key base, or from a product dict)
528     # prod_info.install_dir : corresponds to what is specified in product pyconf (usually "base" for prerequisites)
529     if ( ("install_dir" in prod_info and prod_info.install_dir == "base") # product is declared as base in its own config 
530                                       or ("base" in prod_info  and prod_info.base != "no") ):  # product is declared as base in the application
531         # a product goes in base if install_dir is set to base, or if product was declared based in application pyconf
532         in_base = True
533
534     # check desactivation of base at application level
535     if ( "base" in prod_info  and prod_info.base == "no"):
536         in_base = False
537
538     if in_base:
539         install_dir = get_base_install_dir(config, prod_info, version)
540         install_mode = "base"
541     else:
542         if ("install_mode" in prod_info and prod_info.install_mode in ["implicit", "base"]) or\
543            ("install_dir" not in prod_info or prod_info.install_dir == "base"):
544             # the check to "base" comes from the package case were base mode is changed dynamically 
545             # to create a package launcher.
546
547             # Set it to the default value (in application directory)
548             install_mode = "implicit"
549             if ( src.appli_test_property(config,"single_install_dir", "yes") and 
550                  src.product.product_test_property(prod_info,"single_install_dir", "yes")):
551                 # when single_install_dir mode is activated in tha application
552                 # we use config.INTERNAL.config.single_install_dir for products 
553                 # having single_install_dir property
554                 install_dir = os.path.join(config.APPLICATION.workdir,
555                                            config.INTERNAL.config.install_dir,
556                                            config.INTERNAL.config.single_install_dir)
557             elif ( src.appli_test_property(config,"pip", "yes") and 
558                    src.product.product_test_property(prod_info,"pip", "yes") and
559                    src.appli_test_property(config,"pip_install_dir", "python") ):
560                 # when pip mode is activated in the application
561                 # and product is pip, and pip_install_dir is set to python 
562                 # we assume python in installed in install_dir/Python
563                 install_dir = os.path.join(config.APPLICATION.workdir,
564                                            config.INTERNAL.config.install_dir,
565                                            "Python")   
566             else:
567                 install_dir = os.path.join(config.APPLICATION.workdir,
568                                            config.INTERNAL.config.install_dir,
569                                            prod_info.name)
570         else:
571             install_dir = prod_info.install_dir
572             install_mode = "value"
573
574     return install_dir,install_mode
575
576 def get_base_install_dir(config, prod_info, version):
577     """Compute the installation directory of a product in base 
578     
579     :param config Config: The global configuration
580     :param product_info Config: The configuration specific to 
581                                the product
582     :param version str: The version of the product    
583     :param base str: This corresponds to the value given by user in its 
584                      application.pyconf for the specific product. If "yes", the
585                      user wants the product to be in base. If "no", he wants the
586                      product to be in the application workdir.
587                      if it contains something else, is is interpreted as the name 
588                      of a base we build for module load.
589     :return: The path of the product installation
590     :rtype: str
591     """    
592     
593     # get rid of / to avoid create subdirectories cf sat #18546
594     version_wslash=version.replace("/", "_") 
595
596     if ( src.appli_test_property(config,"pip", "yes") and 
597          src.product.product_test_property(prod_info,"pip", "yes") and
598          src.appli_test_property(config,"pip_install_dir", "python") ):
599          # when pip mode is activated in the application
600          # and product is pip, and pip_install_dir is set to python 
601         python_info=get_product_config(config, "Python")
602         return python_info.install_dir
603
604     base_path = src.get_base_path(config) 
605     if "base" in prod_info and prod_info.base != "no" and prod_info.base != "yes":
606         # we are in the case of a named base
607         prod_dir = os.path.join(base_path, "apps", prod_info.base, prod_info.name, version_wslash)
608         return prod_dir
609     
610     prod_dir = os.path.join(base_path, prod_info.name + "-" + version_wslash)
611     if not os.path.exists(prod_dir):
612         return os.path.join(prod_dir, "config-1")
613     
614     exists, install_dir = check_config_exists(config, prod_dir, prod_info)
615     if exists:
616         return install_dir
617     
618     # Find the first config-<i> directory that is available in the product
619     # directory
620     found = False 
621     label = 1
622     while not found:
623         install_dir = os.path.join(prod_dir, "config-%i" % label)
624         if os.path.exists(install_dir):
625             label+=1
626         else:
627             found = True
628             
629     return install_dir
630
631 def add_compile_config_file(p_info, config):
632     '''Execute the proper configuration command(s)
633        in the product build directory.
634
635     :param p_info Config: The specific config of the product
636     :param config Config: The global configuration
637     '''
638     # Create the compile config
639     # DBG.write("add_compile_config_file", p_info, True)
640     res = src.pyconf.Config()
641     res.addMapping(p_info.name, src.pyconf.Mapping(res), "")
642     res[p_info.name]= p_info.version
643
644     depprod=[]
645     for d in p_info.depend:
646         depprod.append(d)
647     if "build_depend" in p_info:
648         for d in p_info.build_depend:
649             depprod.append(d)
650     for prod_name in depprod:
651       if prod_name not in res:
652         res.addMapping(prod_name, src.pyconf.Mapping(res), "")
653       prod_dep_info = src.product.get_product_config(config, prod_name, False)
654       res[prod_name] = prod_dep_info.version
655     # Write it in the install directory of the product
656     # This file is for automatic reading/checking
657     # see check_config_exists method
658     afilename = CONFIG_FILENAME + p_info.name + ".pyconf"
659     aFile = os.path.join(p_info.install_dir, afilename)
660     with open(aFile, 'w') as f:
661       res.__save__(f)
662
663     # this file is not mandatory, is for human eye reading
664     afilename = PRODUCT_FILENAME + p_info.name + ".pyconf"
665     aFile = os.path.join(p_info.install_dir, afilename)
666     try:
667       with open(aFile, 'w') as f:
668         p_info.__save__(f, evaluated=True) # evaluated expressions mode
669     except:
670       # sometime some information cannot be evaluated.
671       # for example, in the context of non VCS archives, information on git server is not available.
672       DBG.write("Warning : sat was not able to evaluate and write down some information in file %s" % aFile)
673   
674
675 def check_config_exists(config, prod_dir, prod_info, verbose=False):
676     """\
677     Verify that the installation directory of a product in a base exists.
678     Check all the config-<i>/sat-config.py files found for correspondence
679     with current config and prod_info depend-version-tags
680     
681     :param config Config: The global configuration
682     :param prod_dir str: The product installation directory path 
683                          (without config-<i>)
684     :param product_info Config: The configuration specific to 
685                                the product
686     :return: True or false is the installation is found or not 
687              and if it is found, the path of the found installation
688     :rtype: (boolean, str)
689     """
690     # check if the directories or files of the directory corresponds to the
691     # directory installation of the product
692     if os.path.isdir(prod_dir):
693       l_dir_and_files = os.listdir(prod_dir)
694     else:
695       raise Exception("Inexisting directory '%s'" % prod_dir)
696
697     DBG.write("check_config_exists 000",  (prod_dir, l_dir_and_files), verbose)
698     DBG.write("check_config_exists 111",  prod_info, verbose)
699
700     depend_all=[]
701     if "depend" in prod_info:
702         for d in prod_info.depend:
703             depend_all.append(d)
704     if "build_depend" in prod_info:
705         for d in prod_info.build_depend:
706             depend_all.append(d)
707     for dir_or_file in l_dir_and_files:
708         oExpr = re.compile(config_expression)
709         if not(oExpr.search(dir_or_file)):
710             # in mode BASE, not config-<i>, not interesting
711             # DBG.write("not interesting", dir_or_file, True)
712             continue
713         # check if there is the file sat-config.pyconf file in the installation
714         # directory    
715         afilename = CONFIG_FILENAME + prod_info.name + ".pyconf"
716         config_file = os.path.join(prod_dir, dir_or_file, afilename)
717         DBG.write("check_config_exists 222", config_file, verbose)
718         if not os.path.exists(config_file):
719             continue
720         
721         # check if there is the config described in the file corresponds the 
722         # dependencies of the product
723         config_corresponds = True    
724         compile_cfg = src.pyconf.Config(config_file)
725         for prod_dep in depend_all:
726             # if the dependency is not in the config, 
727             # the config does not correspond
728             if prod_dep not in compile_cfg:
729                 config_corresponds = False
730                 break
731             else:
732                 prod_dep_info = get_product_config(config, prod_dep, False)
733                 # If the version of the dependency does not correspond, 
734                 # the config does not correspond
735                 if prod_dep_info.version != compile_cfg[prod_dep]:
736                     config_corresponds = False
737                     break
738
739         if config_corresponds:
740           for prod_name in compile_cfg:
741             # assume new compatibility with prod_name in sat-config.pyconf files
742             if prod_name == prod_info.name:
743               if prod_info.version == compile_cfg[prod_name]:
744                 DBG.write("check_config_exists OK 333", compile_cfg, verbose)
745                 pass
746               else: # no correspondence with newer with prod_name sat-config.pyconf files
747                 config_corresponds = False
748                 break
749             else:
750               # as old compatibility without prod_name sat-config.pyconf files
751               if prod_name not in depend_all:
752                 # here there is an unexpected depend in an old compilation
753                 config_corresponds = False
754                 break
755         
756         if config_corresponds: # returns (and stops) at first correspondence found
757             DBG.write("check_config_exists OK 444", dir_or_file, verbose)
758             return True, os.path.join(prod_dir, dir_or_file)
759
760     # no correspondence found
761     return False, None
762             
763             
764     
765 def get_products_infos(lproducts, config):
766     """Get the specific configuration of a list of products
767     
768     :param lproducts List: The list of product names
769     :param config Config: The global configuration
770     :return: the list of tuples 
771              (product name, specific configuration of the product)
772     :rtype: [(str, Config)]
773     """
774     products_infos = []
775     # Loop on product names
776     for prod in lproducts:       
777         # Get the specific configuration of the product
778         prod_info = get_product_config(config, prod)
779         if prod_info is not None:
780             products_infos.append((prod, prod_info))
781         else:
782             msg = _("The %s product has no definition in the configuration.") % prod
783             raise src.SatException(msg)
784     return products_infos
785
786
787 def get_products_list(options, cfg, logger):
788     """
789     method that gives the product list with their informations from
790     configuration regarding the passed options.
791
792     :param options Options: The Options instance that stores the commands arguments
793     :param cfg Config: The global configuration
794     :param logger Logger: The logger instance to use for the display and logging
795     :return: The list of (product name, product_informations).
796     :rtype: List
797     """
798     # Get the products to be prepared, regarding the options
799     if options.products is None:
800         # No options, get all products sources
801         products = cfg.APPLICATION.products
802     else:
803         # if option --products, check that all products of the command line
804         # are present in the application.
805         """products = options.products
806         for p in products:
807             if p not in cfg.APPLICATION.products:
808                 raise src.SatException(_("Product %(product)s "
809                             "not defined in application %(application)s") %
810                         { 'product': p, 'application': cfg.VARS.application} )"""
811
812         products = src.getProductNames(cfg, options.products, logger)
813
814     # Construct the list of tuple containing
815     # the products name and their definition
816     resAll = src.product.get_products_infos(products, cfg)
817
818     # if the property option was passed, filter the list
819     if options.properties: # existing properties
820       ok = []
821       ko = []
822       res =[]
823       prop, value = options.properties # for example 'is_SALOME_module', 'yes'
824       if value[0] == '!':
825           for p_name, p_info in resAll:
826             try:
827               if p_info.properties[prop] == value[1:]:
828                 ko.append(p_name)
829               else:
830                 res.append((p_name, p_info))
831                 ok.append(p_name)
832             except:
833               res.append((p_name, p_info))
834               ok.append(p_name)
835       else:
836           for p_name, p_info in resAll:
837             try:
838               if p_info.properties[prop] == value:
839                 res.append((p_name, p_info))
840                 ok.append(p_name)
841               else:
842                 ko.append(p_name)
843             except:
844               ko.append(p_name)
845
846       if len(ok) != len(resAll):
847         logger.trace("on properties %s\n products accepted:\n %s\n products rejected:\n %s\n" %
848                        (options.properties, PP.pformat(sorted(ok)), PP.pformat(sorted(ko))))
849       else:
850         logger.warning("properties %s\n seems useless with no products rejected" %
851                        (options.properties))
852     else:
853       res = resAll # not existing properties as all accepted
854
855     return res
856
857
858 def get_product_dependencies(config, product_name, product_info):
859     """\
860     Get the list of products that are 
861     in the product_info dependencies
862     
863     :param config Config: The global configuration
864     :param product_info Config: The configuration specific to 
865                                the product
866     :return: the list of products in dependence
867     :rtype: list
868     """
869     from compile import get_dependencies_graph, depth_search_graph
870     all_products_infos = get_products_infos(
871                              config.APPLICATION.products,
872                              config)
873     all_products_graph=get_dependencies_graph(all_products_infos)
874     res=[]
875     res=depth_search_graph(all_products_graph, product_name, res)
876     return res[1:]  # remove the product himself (in first position)
877
878 def check_installation(config, product_info):
879     """\
880     Verify if a product is well installed. Checks install directory presence
881     and some additional files if it is defined in the config 
882     
883     :param product_info Config: The configuration specific to 
884                                the product
885     :return: True if it is well installed
886     :rtype: boolean
887     """
888     # don't check products that are not compiled
889     if not product_compiles(product_info):
890         return True
891
892     if product_is_native(product_info):
893         # check a system product
894         check_cmd=src.system.get_pkg_check_cmd(config.VARS.dist_name)
895         run_pkg,build_pkg=src.product.check_system_dep(config.VARS.dist, check_cmd, product_info)
896         build_dep_ko=[]
897         for pkg in build_pkg:
898             if "KO" in build_pkg[pkg]:
899                build_dep_ko.append(pkg) 
900         if build_dep_ko:
901               # the product is not installed : display message and return error status
902               msg="Please install them with %s before compiling salome" % check_cmd[0]
903               print("\nmissing compile time dependencies : ")
904               for md in build_dep_ko: 
905                   print(md)
906               print(msg)
907               return False
908         else:
909             return True    
910
911     install_dir = product_info.install_dir
912     if src.product.product_is_fixed(product_info):
913         # we check directly the install dir only for fixed products
914         # (there is no pyconf file in that case)
915         if not os.path.exists(install_dir):
916             return False
917     else:
918         filename = CONFIG_FILENAME + product_info.name + ".pyconf"
919         if not os.path.exists(os.path.join(install_dir, filename)): 
920             return False
921
922     # check extra files if specified in present_files.install section
923     if ("present_files" in product_info and 
924         "install" in product_info.present_files):
925         for file_relative_path in product_info.present_files.install:
926             file_path = os.path.join(install_dir, file_relative_path)
927             if not os.path.exists(file_path):
928                 return False
929     return True
930
931 def check_source(product_info):
932     """Verify if a sources of product is preset. Checks source directory presence
933     
934     :param product_info Config: The configuration specific to 
935                                the product
936     :return: True if it is well installed
937     :rtype: boolean
938     """
939     source_dir = product_info.source_dir
940     if not os.path.exists(source_dir):
941         return False
942     if ("present_files" in product_info and 
943         "source" in product_info.present_files):
944         for file_relative_path in product_info.present_files.source:
945             file_path = os.path.join(source_dir, file_relative_path)
946             if not os.path.exists(file_path):
947                 return False
948     return True
949
950 def product_is_salome(product_info):
951     """Know if a product is a SALOME module
952     
953     :param product_info Config: The configuration specific to 
954                                the product
955     :return: True if the product is a SALOME module, else False
956     :rtype: boolean
957     """
958     return ("properties" in product_info and
959             "is_SALOME_module" in product_info.properties and
960             product_info.properties.is_SALOME_module == "yes")
961
962 def product_is_configuration(product_info):
963     """Know if a product is a configuration module
964     
965     :param product_info Config: The configuration specific to 
966                                the product
967     :return: True if the product is a configuration module, else False
968     :rtype: boolean
969     """
970     return ("properties" in product_info and
971             "configure_dependency" in product_info.properties and
972             product_info.properties.configure_dependency == "yes")
973
974 def product_is_fixed(product_info):
975     """Know if a product is fixed
976     
977     :param product_info Config: The configuration specific to 
978                                the product
979     :return: True if the product is fixed, else False
980     :rtype: boolean
981     """
982     get_src = product_info.get_source
983     return get_src.lower() == 'fixed'
984
985 def product_is_native(product_info):
986     """Know if a product is native
987     
988     :param product_info Config: The configuration specific to 
989                                the product
990     :return: True if the product is native, else False
991     :rtype: boolean
992     """
993     get_src = product_info.get_source
994     return get_src.lower() == 'native'
995
996 def product_is_dev(product_info):
997     """Know if a product is in dev mode
998     
999     :param product_info Config: The configuration specific to 
1000                                the product
1001     :return: True if the product is in dev mode, else False
1002     :rtype: boolean
1003     """
1004     dev = product_info.dev
1005     res = (dev.lower() == 'yes')
1006     DBG.write('product_is_dev %s' % product_info.name, res)
1007     # if product_info.name == "XDATA": return True #test #10569
1008     return res
1009
1010 def product_is_hpc(product_info):
1011     """Know if a product is in hpc mode
1012     
1013     :param product_info Config: The configuration specific to 
1014                                the product
1015     :return: True if the product is in hpc mode, else False
1016     :rtype: boolean
1017     """
1018     hpc = product_info.hpc
1019     res = (hpc.lower() == 'yes')
1020     return res
1021
1022 def product_is_debug(product_info):
1023     """Know if a product is in debug mode
1024     
1025     :param product_info Config: The configuration specific to 
1026                                the product
1027     :return: True if the product is in debug mode, else False
1028     :rtype: boolean
1029     """
1030     debug = product_info.debug
1031     return debug.lower() == 'yes'
1032
1033 def product_is_verbose(product_info):
1034     """Know if a product is in verbose mode
1035     
1036     :param product_info Config: The configuration specific to 
1037                                the product
1038     :return: True if the product is in verbose mode, else False
1039     :rtype: boolean
1040     """
1041     verbose = product_info.verbose
1042     return verbose.lower() == 'yes'
1043
1044 def product_is_autotools(product_info):
1045     """Know if a product is compiled using the autotools
1046     
1047     :param product_info Config: The configuration specific to 
1048                                the product
1049     :return: True if the product is autotools, else False
1050     :rtype: boolean
1051     """
1052     build_src = product_info.build_source
1053     return build_src.lower() == 'autotools'
1054
1055 def product_is_cmake(product_info):
1056     """Know if a product is compiled using the cmake
1057     
1058     :param product_info Config: The configuration specific to 
1059                                the product
1060     :return: True if the product is cmake, else False
1061     :rtype: boolean
1062     """
1063     build_src = product_info.build_source
1064     return build_src.lower() == 'cmake'
1065
1066 def product_is_vcs(product_info):
1067     """Know if a product is download using git, svn or cvs (not archive)
1068     
1069     :param product_info Config: The configuration specific to 
1070                                the product
1071     :return: True if the product is vcs, else False
1072     :rtype: boolean
1073     """
1074     return product_info.get_source in AVAILABLE_VCS
1075
1076 def product_is_smesh_plugin(product_info):
1077     """Know if a product is a SMESH plugin
1078     
1079     :param product_info Config: The configuration specific to 
1080                                the product
1081     :return: True if the product is a SMESH plugin, else False
1082     :rtype: boolean
1083     """
1084     return ("properties" in product_info and
1085             "smesh_plugin" in product_info.properties and
1086             product_info.properties.smesh_plugin == "yes")
1087
1088 def product_is_cpp(product_info):
1089     """Know if a product is cpp
1090     
1091     :param product_info Config: The configuration specific to 
1092                                the product
1093     :return: True if the product is a cpp, else False
1094     :rtype: boolean
1095     """
1096     return ("properties" in product_info and
1097             "cpp" in product_info.properties and
1098             product_info.properties.cpp == "yes")
1099
1100 def product_compiles(product_info):
1101     """\
1102     Know if a product compiles or not 
1103     (some products do not have a compilation procedure)
1104     
1105     :param product_info Config: The configuration specific to 
1106                                the product
1107     :return: True if the product compiles, else False
1108     :rtype: boolean
1109     """
1110     return not("properties" in product_info and
1111             "compilation" in product_info.properties and
1112             product_info.properties.compilation == "no")
1113
1114 def product_has_script(product_info):
1115     """Know if a product has a compilation script
1116     
1117     :param product_info Config: The configuration specific to 
1118                                the product
1119     :return: True if the product it has a compilation script, else False
1120     :rtype: boolean
1121     """
1122     if "build_source" not in product_info:
1123         # Native case
1124         return False
1125     build_src = product_info.build_source
1126     return build_src.lower() == 'script'
1127
1128 def product_has_env_script(product_info):
1129     """Know if a product has an environment script
1130     
1131     :param product_info Config: The configuration specific to 
1132                                the product
1133     :return: True if the product it has an environment script, else False
1134     :rtype: boolean
1135     """
1136     return "environ" in product_info and "env_script" in product_info.environ
1137
1138 def product_has_patches(product_info):
1139     """Know if a product has one or more patches
1140     
1141     :param product_info Config: The configuration specific to 
1142                                the product
1143     :return: True if the product has one or more patches
1144     :rtype: boolean
1145     """   
1146     res = ( "patches" in product_info and len(product_info.patches) > 0 )
1147     return res
1148
1149 def product_has_post_script(product_info):
1150     """Know if a product has a post install script
1151     
1152     :param product_info Config: The configuration specific to 
1153                                the product
1154     :return: True if the product has one or more patches
1155     :rtype: boolean
1156     """   
1157     res = ( "post_script" in product_info and len(product_info.post_script) > 0 and not src.architecture.is_windows())
1158     return res
1159
1160 def product_has_logo(product_info):
1161     """Know if a product has a logo (YACSGEN generate)
1162     
1163     :param product_info Config: The configuration specific to 
1164                                the product
1165     :return: The path of the logo if the product has a logo, else False
1166     :rtype: Str
1167     """
1168     if ("properties" in product_info and
1169             "logo" in product_info.properties):
1170         return product_info.properties.logo
1171     else:
1172         return False
1173
1174 def product_has_licence(product_info, path):
1175     """Find out if a product has a licence
1176     
1177     :param product_info Config: The configuration specific to the product
1178     :param path Str: The path where to search for the licence
1179     :return: The name of the licence file (the complete path if it is found in the path, else the name, else False
1180     :rtype: Str
1181     """
1182     if ("properties" in product_info and
1183             "licence" in product_info.properties):
1184         licence_name = product_info.properties.licence
1185         if len(path) > 0:
1186             # search for licence_name in path
1187             # a- consolidate the path into one signe string licence_path
1188             licence_path=path[0]
1189             for lpath in path[1:]:
1190                 licence_path=licence_path+":"+lpath
1191             licence_path_list=licence_path.split(":")
1192             licence_fullname = src.find_file_in_lpath(licence_name, licence_path_list)
1193             if licence_fullname:
1194                 return licence_fullname
1195
1196         # if the search of licence in path failed, we return its name (not the full path) 
1197         return licence_name
1198
1199     else:
1200         return False  # product has no licence
1201
1202 def product_has_salome_gui(product_info):
1203     """Know if a product has a SALOME gui
1204     
1205     :param product_info Config: The configuration specific to 
1206                                the product
1207     :return: True if the product has a SALOME gui, else False
1208     :rtype: Boolean
1209     """
1210     return ("properties" in product_info and
1211             "has_salome_gui" in product_info.properties and
1212             product_info.properties.has_salome_gui == "yes")
1213
1214 def product_is_mpi(product_info):
1215     """Know if a product has openmpi in its dependencies
1216     
1217     :param product_info Config: The configuration specific to 
1218                                the product
1219     :return: True if the product has openmpi inits dependencies
1220     :rtype: boolean
1221     """
1222     return "openmpi" in product_info.depend
1223
1224 def product_is_generated(product_info):
1225     """Know if a product is generated (YACSGEN)
1226     
1227     :param product_info Config: The configuration specific to 
1228                                the product
1229     :return: True if the product is generated
1230     :rtype: boolean
1231     """
1232     return ("properties" in product_info and
1233             "generate" in product_info.properties and
1234             product_info.properties.generate == "yes")
1235
1236 def product_is_compile_time(product_info):
1237     """Know if a product is only used at compile time
1238     
1239     :param product_info Config: The configuration specific to 
1240                                the product
1241     :return: True if the product is only used at compile time
1242     :rtype: boolean
1243     """
1244     return ("properties" in product_info and
1245             "compile_time" in product_info.properties and
1246             product_info.properties.compile_time == "yes")
1247
1248 def product_is_compile_and_runtime(product_info):
1249     """Know if a product is only used at compile time
1250     
1251     :param product_info Config: The configuration specific to 
1252                                the product
1253     :return: True if the product is only used at compile time
1254     :rtype: boolean
1255     """
1256     return ("properties" in product_info and
1257             "compile_and_runtime" in product_info.properties and
1258             product_info.properties.compile_and_runtime == "yes")
1259
1260
1261
1262 def product_test_property(product_info, property_name, property_value):
1263     """Generic function to test if a product has a property set to a value
1264     
1265     :param product_info Config: The configuration specific to 
1266                                the product
1267     :param property_name : The name of the property to check
1268     :param property_value : The value of the property to test
1269     :return: True if the product has the property and the property is set to property_value
1270     :rtype: boolean
1271     """
1272     # first check if product has the property
1273     if not ("properties" in product_info and
1274             property_name in product_info.properties):
1275         return False
1276   
1277     # then check to the property is set to property_value
1278     eval_expression = 'product_info.properties.%s == "%s"' % (property_name,property_value)
1279     result = eval(eval_expression)
1280     return result
1281
1282 def check_system_dep(distrib, check_cmd, product_info):
1283     """Search for system dependencies, check if installed
1284     :param dist : The linux ditribution (CO7,DB10...)
1285     :param check_cmd Config: The command to use for checking (rpm/apt)
1286     :param product_info Config: The configuration specific to the product
1287     :rtype: two dictionnaries for runtime and compile time dependencies with text status
1288     """
1289     runtime_dep={}
1290     build_dep={}
1291
1292     if "system_info" in product_info:
1293
1294         sysinfo=product_info.system_info
1295         additional_sysinfo = None
1296
1297         for key in sysinfo :
1298             if distrib in key :
1299                 additional_sysinfo = sysinfo[key]
1300
1301         if check_cmd[0]=="rpm":
1302             if "rpm" in sysinfo:
1303                 for pkg in sysinfo.rpm:
1304                     runtime_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1305             if "rpm_dev" in sysinfo:
1306                 for pkg in sysinfo.rpm_dev:
1307                     build_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1308             if additional_sysinfo :
1309                 if "rpm" in additional_sysinfo:
1310                     for pkg in additional_sysinfo.rpm:
1311                         runtime_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1312                 if "rpm_dev" in additional_sysinfo:
1313                     for pkg in additional_sysinfo.rpm_dev:
1314                         build_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1315         if check_cmd[0]=="apt":
1316             if "apt" in sysinfo:
1317                 for pkg in sysinfo.apt:
1318                     runtime_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1319             if "apt_dev" in sysinfo:
1320                 for pkg in sysinfo.apt_dev:
1321                     build_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1322             if additional_sysinfo :
1323                 if "apt" in additional_sysinfo:
1324                     for pkg in additional_sysinfo.apt:
1325                         runtime_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1326                 if "apt_dev" in additional_sysinfo:
1327                     for pkg in additional_sysinfo.apt_dev:
1328                         build_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1329
1330     return runtime_dep,build_dep
1331
1332
1333 def get_product_components(product_info):
1334     """Get the component list to generate with the product
1335     
1336     :param product_info Config: The configuration specific to 
1337                                the product
1338     :return: The list of names of the components
1339     :rtype: List
1340     
1341     """
1342     if not product_is_generated(product_info):
1343         return []
1344     
1345     compo_list = []
1346     if "component_name" in product_info:
1347         compo_list = product_info.component_name
1348     
1349         if isinstance(compo_list, str):
1350             compo_list = [ compo_list ]
1351
1352     return compo_list
1353 def product_is_wheel(product_info):
1354     """ tells whether a product is a wheel
1355     
1356     :param product_info Config: The configuration specific to 
1357                                the product
1358     :return: True if the product has a wheel, else False
1359     :rtype: Boolean
1360     """
1361     return ("properties" in product_info and
1362             "is_wheel" in product_info.properties and
1363             product_info.properties.is_wheel == "yes")
1364