Salome HOME
gestion de path multiples dans la configuration projet
[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:
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       for p_name, p_info in resAll:
801         try:
802           if p_info.properties[prop] == value:
803             res.append((p_name, p_info))
804             ok.append(p_name)
805           else:
806             ko.append(p_name)
807         except:
808           ko.append(p_name)
809
810       if len(ok) != len(resAll):
811         logger.trace("on properties %s\n products accepted:\n %s\n products rejected:\n %s\n" %
812                        (options.properties, PP.pformat(sorted(ok)), PP.pformat(sorted(ko))))
813       else:
814         logger.warning("properties %s\n seems useless with no products rejected" %
815                        (options.properties))
816     else:
817       res = resAll # not existing properties as all accepted
818
819     return res
820
821
822 def get_product_dependencies(config, product_name, product_info):
823     """\
824     Get the list of products that are 
825     in the product_info dependencies
826     
827     :param config Config: The global configuration
828     :param product_info Config: The configuration specific to 
829                                the product
830     :return: the list of products in dependence
831     :rtype: list
832     """
833     from compile import get_dependencies_graph, depth_search_graph
834     all_products_infos = get_products_infos(
835                              config.APPLICATION.products,
836                              config)
837     all_products_graph=get_dependencies_graph(all_products_infos)
838     res=[]
839     res=depth_search_graph(all_products_graph, product_name, res)
840     return res[1:]  # remove the product himself (in first position)
841
842 def check_installation(config, product_info):
843     """\
844     Verify if a product is well installed. Checks install directory presence
845     and some additional files if it is defined in the config 
846     
847     :param product_info Config: The configuration specific to 
848                                the product
849     :return: True if it is well installed
850     :rtype: boolean
851     """
852     # don't check products that are not compiled
853     if not product_compiles(product_info):
854         return True
855
856     if product_is_native(product_info):
857         # check a system product
858         check_cmd=src.system.get_pkg_check_cmd(config.VARS.dist_name)
859         run_pkg,build_pkg=src.product.check_system_dep(config.VARS.dist, check_cmd, product_info)
860         build_dep_ko=[]
861         for pkg in build_pkg:
862             if "KO" in build_pkg[pkg]:
863                build_dep_ko.append(pkg) 
864         if build_dep_ko:
865               # the product is not installed : display message and return error status
866               msg="Please install them with %s before compiling salome" % check_cmd[0]
867               print("\nmissing compile time dependencies : ")
868               for md in build_dep_ko: 
869                   print(md)
870               print(msg)
871               return False
872         else:
873             return True    
874
875     install_dir = product_info.install_dir
876     if src.product.product_is_fixed(product_info):
877         # we check directly the install dir only for fixed products
878         # (there is no pyconf file in that case)
879         if not os.path.exists(install_dir):
880             return False
881     else:
882         filename = CONFIG_FILENAME + product_info.name + ".pyconf"
883         if not os.path.exists(os.path.join(install_dir, filename)): 
884             return False
885
886     # check extra files if specified in present_files.install section
887     if ("present_files" in product_info and 
888         "install" in product_info.present_files):
889         for file_relative_path in product_info.present_files.install:
890             file_path = os.path.join(install_dir, file_relative_path)
891             if not os.path.exists(file_path):
892                 return False
893     return True
894
895 def check_source(product_info):
896     """Verify if a sources of product is preset. Checks source directory presence
897     
898     :param product_info Config: The configuration specific to 
899                                the product
900     :return: True if it is well installed
901     :rtype: boolean
902     """
903     source_dir = product_info.source_dir
904     if not os.path.exists(source_dir):
905         return False
906     if ("present_files" in product_info and 
907         "source" in product_info.present_files):
908         for file_relative_path in product_info.present_files.source:
909             file_path = os.path.join(source_dir, file_relative_path)
910             if not os.path.exists(file_path):
911                 return False
912     return True
913
914 def product_is_salome(product_info):
915     """Know if a product is a SALOME module
916     
917     :param product_info Config: The configuration specific to 
918                                the product
919     :return: True if the product is a SALOME module, else False
920     :rtype: boolean
921     """
922     return ("properties" in product_info and
923             "is_SALOME_module" in product_info.properties and
924             product_info.properties.is_SALOME_module == "yes")
925
926 def product_is_fixed(product_info):
927     """Know if a product is fixed
928     
929     :param product_info Config: The configuration specific to 
930                                the product
931     :return: True if the product is fixed, else False
932     :rtype: boolean
933     """
934     get_src = product_info.get_source
935     return get_src.lower() == 'fixed'
936
937 def product_is_native(product_info):
938     """Know if a product is native
939     
940     :param product_info Config: The configuration specific to 
941                                the product
942     :return: True if the product is native, else False
943     :rtype: boolean
944     """
945     get_src = product_info.get_source
946     return get_src.lower() == 'native'
947
948 def product_is_dev(product_info):
949     """Know if a product is in dev mode
950     
951     :param product_info Config: The configuration specific to 
952                                the product
953     :return: True if the product is in dev mode, else False
954     :rtype: boolean
955     """
956     dev = product_info.dev
957     res = (dev.lower() == 'yes')
958     DBG.write('product_is_dev %s' % product_info.name, res)
959     # if product_info.name == "XDATA": return True #test #10569
960     return res
961
962 def product_is_hpc(product_info):
963     """Know if a product is in hpc mode
964     
965     :param product_info Config: The configuration specific to 
966                                the product
967     :return: True if the product is in hpc mode, else False
968     :rtype: boolean
969     """
970     hpc = product_info.hpc
971     res = (hpc.lower() == 'yes')
972     return res
973
974 def product_is_debug(product_info):
975     """Know if a product is in debug mode
976     
977     :param product_info Config: The configuration specific to 
978                                the product
979     :return: True if the product is in debug mode, else False
980     :rtype: boolean
981     """
982     debug = product_info.debug
983     return debug.lower() == 'yes'
984
985 def product_is_verbose(product_info):
986     """Know if a product is in verbose mode
987     
988     :param product_info Config: The configuration specific to 
989                                the product
990     :return: True if the product is in verbose mode, else False
991     :rtype: boolean
992     """
993     verbose = product_info.verbose
994     return verbose.lower() == 'yes'
995
996 def product_is_autotools(product_info):
997     """Know if a product is compiled using the autotools
998     
999     :param product_info Config: The configuration specific to 
1000                                the product
1001     :return: True if the product is autotools, else False
1002     :rtype: boolean
1003     """
1004     build_src = product_info.build_source
1005     return build_src.lower() == 'autotools'
1006
1007 def product_is_cmake(product_info):
1008     """Know if a product is compiled using the cmake
1009     
1010     :param product_info Config: The configuration specific to 
1011                                the product
1012     :return: True if the product is cmake, else False
1013     :rtype: boolean
1014     """
1015     build_src = product_info.build_source
1016     return build_src.lower() == 'cmake'
1017
1018 def product_is_vcs(product_info):
1019     """Know if a product is download using git, svn or cvs (not archive)
1020     
1021     :param product_info Config: The configuration specific to 
1022                                the product
1023     :return: True if the product is vcs, else False
1024     :rtype: boolean
1025     """
1026     return product_info.get_source in AVAILABLE_VCS
1027
1028 def product_is_smesh_plugin(product_info):
1029     """Know if a product is a SMESH plugin
1030     
1031     :param product_info Config: The configuration specific to 
1032                                the product
1033     :return: True if the product is a SMESH plugin, else False
1034     :rtype: boolean
1035     """
1036     return ("properties" in product_info and
1037             "smesh_plugin" in product_info.properties and
1038             product_info.properties.smesh_plugin == "yes")
1039
1040 def product_is_cpp(product_info):
1041     """Know if a product is cpp
1042     
1043     :param product_info Config: The configuration specific to 
1044                                the product
1045     :return: True if the product is a cpp, else False
1046     :rtype: boolean
1047     """
1048     return ("properties" in product_info and
1049             "cpp" in product_info.properties and
1050             product_info.properties.cpp == "yes")
1051
1052 def product_compiles(product_info):
1053     """\
1054     Know if a product compiles or not 
1055     (some products do not have a compilation procedure)
1056     
1057     :param product_info Config: The configuration specific to 
1058                                the product
1059     :return: True if the product compiles, else False
1060     :rtype: boolean
1061     """
1062     return not("properties" in product_info and
1063             "compilation" in product_info.properties and
1064             product_info.properties.compilation == "no")
1065
1066 def product_has_script(product_info):
1067     """Know if a product has a compilation script
1068     
1069     :param product_info Config: The configuration specific to 
1070                                the product
1071     :return: True if the product it has a compilation script, else False
1072     :rtype: boolean
1073     """
1074     if "build_source" not in product_info:
1075         # Native case
1076         return False
1077     build_src = product_info.build_source
1078     return build_src.lower() == 'script'
1079
1080 def product_has_env_script(product_info):
1081     """Know if a product has an environment script
1082     
1083     :param product_info Config: The configuration specific to 
1084                                the product
1085     :return: True if the product it has an environment script, else False
1086     :rtype: boolean
1087     """
1088     return "environ" in product_info and "env_script" in product_info.environ
1089
1090 def product_has_patches(product_info):
1091     """Know if a product has one or more patches
1092     
1093     :param product_info Config: The configuration specific to 
1094                                the product
1095     :return: True if the product has one or more patches
1096     :rtype: boolean
1097     """   
1098     res = ( "patches" in product_info and len(product_info.patches) > 0 )
1099     return res
1100
1101 def product_has_logo(product_info):
1102     """Know if a product has a logo (YACSGEN generate)
1103     
1104     :param product_info Config: The configuration specific to 
1105                                the product
1106     :return: The path of the logo if the product has a logo, else False
1107     :rtype: Str
1108     """
1109     if ("properties" in product_info and
1110             "logo" in product_info.properties):
1111         return product_info.properties.logo
1112     else:
1113         return False
1114
1115 def product_has_licence(product_info, path):
1116     """Find out if a product has a licence
1117     
1118     :param product_info Config: The configuration specific to the product
1119     :param path Str: The path where to search for the licence
1120     :return: The name of the licence file (the complete path if it is found in the path, else the name, else False
1121     :rtype: Str
1122     """
1123     if ("properties" in product_info and
1124             "licence" in product_info.properties):
1125         licence_name = product_info.properties.licence
1126         if len(path) > 0:
1127             # search for licence_name in path
1128             # a- consolidate the path into one signe string licence_path
1129             licence_path=path[0]
1130             for lpath in path[1:]:
1131                 licence_path=licence_path+":"+lpath
1132             licence_path_list=licence_path.split(":")
1133             licence_fullname = src.find_file_in_lpath(licence_name, licence_path_list)
1134             if licence_fullname:
1135                 return licence_fullname
1136
1137         # if the search of licence in path failed, we return its name (not the full path) 
1138         return licence_name
1139
1140     else:
1141         return False  # product has no licence
1142
1143 def product_has_salome_gui(product_info):
1144     """Know if a product has a SALOME gui
1145     
1146     :param product_info Config: The configuration specific to 
1147                                the product
1148     :return: True if the product has a SALOME gui, else False
1149     :rtype: Boolean
1150     """
1151     return ("properties" in product_info and
1152             "has_salome_gui" in product_info.properties and
1153             product_info.properties.has_salome_gui == "yes")
1154
1155 def product_is_mpi(product_info):
1156     """Know if a product has openmpi in its dependencies
1157     
1158     :param product_info Config: The configuration specific to 
1159                                the product
1160     :return: True if the product has openmpi inits dependencies
1161     :rtype: boolean
1162     """
1163     return "openmpi" in product_info.depend
1164
1165 def product_is_generated(product_info):
1166     """Know if a product is generated (YACSGEN)
1167     
1168     :param product_info Config: The configuration specific to 
1169                                the product
1170     :return: True if the product is generated
1171     :rtype: boolean
1172     """
1173     return ("properties" in product_info and
1174             "generate" in product_info.properties and
1175             product_info.properties.generate == "yes")
1176
1177 def product_is_compile_time(product_info):
1178     """Know if a product is only used at compile time
1179     
1180     :param product_info Config: The configuration specific to 
1181                                the product
1182     :return: True if the product is only used at compile time
1183     :rtype: boolean
1184     """
1185     return ("properties" in product_info and
1186             "compile_time" in product_info.properties and
1187             product_info.properties.compile_time == "yes")
1188
1189 def product_is_compile_and_runtime(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_and_runtime" in product_info.properties and
1199             product_info.properties.compile_and_runtime == "yes")
1200
1201
1202
1203 def product_test_property(product_info, property_name, property_value):
1204     """Generic function to test if a product has a property set to a value
1205     
1206     :param product_info Config: The configuration specific to 
1207                                the product
1208     :param property_name : The name of the property to check
1209     :param property_value : The value of the property to test
1210     :return: True if the product has the property and the property is set to property_value
1211     :rtype: boolean
1212     """
1213     # first check if product has the property
1214     if not ("properties" in product_info and
1215             property_name in product_info.properties):
1216         return False
1217   
1218     # then check to the property is set to property_value
1219     eval_expression = 'product_info.properties.%s == "%s"' % (property_name,property_value)
1220     result = eval(eval_expression)
1221     return result
1222
1223 def check_system_dep(distrib, check_cmd, product_info):
1224     """Search for system dependencies, check if installed
1225     :param dist : The linux ditribution (CO7,DB10...)
1226     :param check_cmd Config: The command to use for checking (rpm/apt)
1227     :param product_info Config: The configuration specific to the product
1228     :rtype: two dictionnaries for runtime and compile time dependencies with text status
1229     """
1230     runtime_dep={}
1231     build_dep={}
1232
1233     if "system_info" in product_info:
1234
1235         sysinfo=product_info.system_info
1236         additional_sysinfo = None
1237
1238         for key in sysinfo :
1239             if distrib in key :
1240                 additional_sysinfo = sysinfo[key]
1241
1242         if check_cmd[0]=="rpm":
1243             if "rpm" in sysinfo:
1244                 for pkg in sysinfo.rpm:
1245                     runtime_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1246             if "rpm_dev" in sysinfo:
1247                 for pkg in sysinfo.rpm_dev:
1248                     build_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1249             if additional_sysinfo :
1250                 if "rpm" in additional_sysinfo:
1251                     for pkg in additional_sysinfo.rpm:
1252                         runtime_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1253                 if "rpm_dev" in additional_sysinfo:
1254                     for pkg in additional_sysinfo.rpm_dev:
1255                         build_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1256         if check_cmd[0]=="apt":
1257             if "apt" in sysinfo:
1258                 for pkg in sysinfo.apt:
1259                     runtime_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1260             if "apt_dev" in sysinfo:
1261                 for pkg in sysinfo.apt_dev:
1262                     build_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1263             if additional_sysinfo :
1264                 if "apt" in additional_sysinfo:
1265                     for pkg in additional_sysinfo.apt:
1266                         runtime_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1267                 if "apt_dev" in additional_sysinfo:
1268                     for pkg in additional_sysinfo.apt_dev:
1269                         build_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
1270
1271     return runtime_dep,build_dep
1272
1273
1274 def get_product_components(product_info):
1275     """Get the component list to generate with the product
1276     
1277     :param product_info Config: The configuration specific to 
1278                                the product
1279     :return: The list of names of the components
1280     :rtype: List
1281     
1282     """
1283     if not product_is_generated(product_info):
1284         return []
1285     
1286     compo_list = []
1287     if "component_name" in product_info:
1288         compo_list = product_info.component_name
1289     
1290         if isinstance(compo_list, str):
1291             compo_list = [ compo_list ]
1292
1293     return compo_list
1294 def product_is_wheel(product_info):
1295     """ tells whether a product is a wheel
1296     
1297     :param product_info Config: The configuration specific to 
1298                                the product
1299     :return: True if the product has a wheel, else False
1300     :rtype: Boolean
1301     """
1302     return ("properties" in product_info and
1303             "is_wheel" in product_info.properties and
1304             product_info.properties.is_wheel == "yes")
1305