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