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