Salome HOME
spns #34338: post build script is not embedded in archive
[tools/sat.git] / src / product.py
index 67b4d1dd73058cca4142a7beb3e9bc8f0a026383..28948da38087ee5c4a7e8d74505908022fb6c1e1 100644 (file)
@@ -149,7 +149,7 @@ def get_product_config(config, product_name, with_install_dir=True):
         
         # Get the hpc if any
         if 'hpc' in dic_version:
-            hpc = dic_version.hpc
+            hpc = dic_version['hpc']
         elif 'hpc' in config.APPLICATION:
             hpc = config.APPLICATION.hpc
 
@@ -165,7 +165,7 @@ def get_product_config(config, product_name, with_install_dir=True):
     # substitute some character with _ in order to get the correct definition
     # in config.PRODUCTS. This is done because the pyconf tool does not handle
     # the . and - characters 
-    for c in ".-": vv = vv.replace(c, "_")
+    for c in ".-/": vv = vv.replace(c, "_")
 
     prod_info = None
     if product_name in config.PRODUCTS:
@@ -178,7 +178,7 @@ def get_product_config(config, product_name, with_install_dir=True):
         # merge opt_depend in depend
         if prod_info is not None and 'opt_depend' in prod_info:
             for depend in prod_info.opt_depend:
-                if depend in config.APPLICATION.products:
+                if (depend in config.APPLICATION.products) and (depend not in prod_info.depend) :
                     prod_info.depend.append(depend,'')
         
 
@@ -204,6 +204,7 @@ def get_product_config(config, product_name, with_install_dir=True):
            # message to the user in case of non existing path.)
             prod_info.install_dir = version
             prod_info.get_source = "fixed"
+            prod_info.install_mode = "fixed"
         
         # Check if the product is defined as native in the application
         if prod_info is not None:
@@ -312,6 +313,30 @@ Please provide a 'compil_script' key in its definition.""") % product_name
         if os.path.exists(prod_info.compil_script) and not os.access(prod_info.compil_script, os.X_OK):
             DBG.tofix("Compilation script  file is not in 'execute mode'", prod_info.compil_script, True)
     
+    # If the product has a post install script, check the script existence
+    # and if it is executable
+    if product_has_post_script(prod_info):
+        # Check the compil_script key existence
+        
+        # Get the path of the script file
+        # if windows supposed '.bat', if linux supposed '.sh'
+        # but user set extension script file in his pyconf as he wants, no obligation.
+        script = prod_info.post_script
+        script_name = os.path.basename(script)
+        if script == script_name:
+            # Only a name is given. Search in the default directory
+            script_path = src.find_file_in_lpath(script_name, config.PATHS.PRODUCTPATH, "post_scripts")
+            if not script_path:
+                msg = _("Post install script %s not found in") % script_name
+                DBG.tofix(msg, config.PATHS.PRODUCTPATH, True) # say where searched
+                script_path = "%s_(Not_Found_by_Sat!!)" % script_name
+            prod_info.post_script = script_path
+
+       
+        # Check that the script is executable
+        if os.path.exists(prod_info.post_script) and not os.access(prod_info.post_script, os.X_OK):
+            DBG.tofix("Post install script file is not in 'execute mode'", prod_info.post_script, True)
+
     # Get the full paths of all the patches
     if product_has_patches(prod_info):
         patches = []
@@ -367,7 +392,7 @@ Please provide a 'compil_script' key in its definition.""") % product_name
             prod_info.install_dir = prod_info.install_dir_save
         
         # Set the install_dir key
-        prod_info.install_dir = get_install_dir(config, version, prod_info)
+        prod_info.install_dir,prod_info.install_mode = get_install_dir(config, version, prod_info)
                 
     return prod_info
 
@@ -493,29 +518,34 @@ def get_install_dir(config, version, prod_info):
     :param product_info Config: The configuration specific to 
                                the product
     
-    :return: The path of the product installation
-    :rtype: str
+    :return: The path of the product installation and the mode of the install directory (base/implicit/fixed/value)
+    :rtype: str,str
     """
     install_dir = ""
     in_base = False
-    # prod_info.base : corresponds to what is specified in application pyconf (either from the global key, or from a product dict)
+    
+    # prod_info.base : corresponds to what is specified in application pyconf (either from the global key base, or from a product dict)
     # prod_info.install_dir : corresponds to what is specified in product pyconf (usually "base" for prerequisites)
-    if ( ("install_dir" in prod_info and prod_info.install_dir == "base") 
-                                      or ("base" in prod_info  and prod_info.base != "no") ):
+    if ( ("install_dir" in prod_info and prod_info.install_dir == "base") # product is declared as base in its own config 
+                                      or ("base" in prod_info  and prod_info.base != "no") ):  # product is declared as base in the application
         # a product goes in base if install_dir is set to base, or if product was declared based in application pyconf
         in_base = True
 
-    # what was declared in application has precedence over what was said in product pyconf
-    # no_base="yes" has precedence over base == "yes"
-    if ( ("base" in prod_info  and prod_info.base == "no")  or ("no_base" in config.APPLICATION 
-                         and config.APPLICATION.no_base == "yes")):
+    # check desactivation of base at application level
+    if ( "base" in prod_info  and prod_info.base == "no"):
         in_base = False
-    
+
     if in_base:
         install_dir = get_base_install_dir(config, prod_info, version)
+        install_mode = "base"
     else:
-        if "install_dir" not in prod_info or prod_info.install_dir == "base":
+        if ("install_mode" in prod_info and prod_info.install_mode in ["implicit", "base"]) or\
+           ("install_dir" not in prod_info or prod_info.install_dir == "base"):
+            # the check to "base" comes from the package case were base mode is changed dynamically 
+            # to create a package launcher.
+
             # Set it to the default value (in application directory)
+            install_mode = "implicit"
             if ( src.appli_test_property(config,"single_install_dir", "yes") and 
                  src.product.product_test_property(prod_info,"single_install_dir", "yes")):
                 # when single_install_dir mode is activated in tha application
@@ -539,8 +569,9 @@ def get_install_dir(config, version, prod_info):
                                            prod_info.name)
         else:
             install_dir = prod_info.install_dir
+            install_mode = "value"
 
-    return install_dir
+    return install_dir,install_mode
 
 def get_base_install_dir(config, prod_info, version):
     """Compute the installation directory of a product in base 
@@ -558,6 +589,10 @@ def get_base_install_dir(config, prod_info, version):
     :return: The path of the product installation
     :rtype: str
     """    
+    
+    # get rid of / to avoid create subdirectories cf sat #18546
+    version_wslash=version.replace("/", "_") 
+
     if ( src.appli_test_property(config,"pip", "yes") and 
          src.product.product_test_property(prod_info,"pip", "yes") and
          src.appli_test_property(config,"pip_install_dir", "python") ):
@@ -569,10 +604,10 @@ def get_base_install_dir(config, prod_info, version):
     base_path = src.get_base_path(config) 
     if "base" in prod_info and prod_info.base != "no" and prod_info.base != "yes":
         # we are in the case of a named base
-        prod_dir = os.path.join(base_path, "apps", prod_info.base, prod_info.name, version)
+        prod_dir = os.path.join(base_path, "apps", prod_info.base, prod_info.name, version_wslash)
         return prod_dir
     
-    prod_dir = os.path.join(base_path, prod_info.name + "-" + version)
+    prod_dir = os.path.join(base_path, prod_info.name + "-" + version_wslash)
     if not os.path.exists(prod_dir):
         return os.path.join(prod_dir, "config-1")
     
@@ -606,7 +641,13 @@ def add_compile_config_file(p_info, config):
     res.addMapping(p_info.name, src.pyconf.Mapping(res), "")
     res[p_info.name]= p_info.version
 
-    for prod_name in p_info.depend:
+    depprod=[]
+    for d in p_info.depend:
+        depprod.append(d)
+    if "build_depend" in p_info:
+        for d in p_info.build_depend:
+            depprod.append(d)
+    for prod_name in depprod:
       if prod_name not in res:
         res.addMapping(prod_name, src.pyconf.Mapping(res), "")
       prod_dep_info = src.product.get_product_config(config, prod_name, False)
@@ -656,6 +697,13 @@ def check_config_exists(config, prod_dir, prod_info, verbose=False):
     DBG.write("check_config_exists 000",  (prod_dir, l_dir_and_files), verbose)
     DBG.write("check_config_exists 111",  prod_info, verbose)
 
+    depend_all=[]
+    if "depend" in prod_info:
+        for d in prod_info.depend:
+            depend_all.append(d)
+    if "build_depend" in prod_info:
+        for d in prod_info.build_depend:
+            depend_all.append(d)
     for dir_or_file in l_dir_and_files:
         oExpr = re.compile(config_expression)
         if not(oExpr.search(dir_or_file)):
@@ -674,7 +722,7 @@ def check_config_exists(config, prod_dir, prod_info, verbose=False):
         # dependencies of the product
         config_corresponds = True    
         compile_cfg = src.pyconf.Config(config_file)
-        for prod_dep in prod_info.depend:
+        for prod_dep in depend_all:
             # if the dependency is not in the config, 
             # the config does not correspond
             if prod_dep not in compile_cfg:
@@ -700,7 +748,7 @@ def check_config_exists(config, prod_dir, prod_info, verbose=False):
                 break
             else:
               # as old compatibility without prod_name sat-config.pyconf files
-              if prod_name not in prod_info.depend:
+              if prod_name not in depend_all:
                 # here there is an unexpected depend in an old compilation
                 config_corresponds = False
                 break
@@ -773,15 +821,27 @@ def get_products_list(options, cfg, logger):
       ko = []
       res =[]
       prop, value = options.properties # for example 'is_SALOME_module', 'yes'
-      for p_name, p_info in resAll:
-        try:
-          if p_info.properties[prop] == value:
-            res.append((p_name, p_info))
-            ok.append(p_name)
-          else:
-            ko.append(p_name)
-        except:
-          ko.append(p_name)
+      if value[0] == '!':
+          for p_name, p_info in resAll:
+            try:
+              if p_info.properties[prop] == value[1:]:
+                ko.append(p_name)
+              else:
+                res.append((p_name, p_info))
+                ok.append(p_name)
+            except:
+              res.append((p_name, p_info))
+              ok.append(p_name)
+      else:
+          for p_name, p_info in resAll:
+            try:
+              if p_info.properties[prop] == value:
+                res.append((p_name, p_info))
+                ok.append(p_name)
+              else:
+                ko.append(p_name)
+            except:
+              ko.append(p_name)
 
       if len(ok) != len(resAll):
         logger.trace("on properties %s\n products accepted:\n %s\n products rejected:\n %s\n" %
@@ -795,9 +855,9 @@ def get_products_list(options, cfg, logger):
     return res
 
 
-def get_product_dependencies(config, product_info):
+def get_product_dependencies(config, product_name, product_info):
     """\
-    Get recursively the list of products that are 
+    Get the list of products that are 
     in the product_info dependencies
     
     :param config Config: The global configuration
@@ -806,20 +866,14 @@ def get_product_dependencies(config, product_info):
     :return: the list of products in dependence
     :rtype: list
     """
-    if "depend" not in product_info or product_info.depend == []:
-        return []
-    res = []
-    for prod in product_info.depend:
-        if prod == product_info.name:
-            continue
-        if prod not in res:
-            res.append(prod)
-        prod_info = get_product_config(config, prod)
-        dep_prod = get_product_dependencies(config, prod_info)
-        for prod_in_dep in dep_prod:
-            if prod_in_dep not in res:
-                res.append(prod_in_dep)
-    return res
+    from compile import get_dependencies_graph, depth_search_graph
+    all_products_infos = get_products_infos(
+                             config.APPLICATION.products,
+                             config)
+    all_products_graph=get_dependencies_graph(all_products_infos)
+    res=[]
+    res=depth_search_graph(all_products_graph, product_name, res)
+    return res[1:]  # remove the product himself (in first position)
 
 def check_installation(config, product_info):
     """\
@@ -831,22 +885,38 @@ def check_installation(config, product_info):
     :return: True if it is well installed
     :rtype: boolean
     """
+    # don't check products that are not compiled
     if not product_compiles(product_info):
         return True
 
+    if product_is_native(product_info):
+        # check a system product
+        check_cmd=src.system.get_pkg_check_cmd(config.VARS.dist_name)
+        run_pkg,build_pkg=src.product.check_system_dep(config.VARS.dist, check_cmd, product_info)
+        build_dep_ko=[]
+        for pkg in build_pkg:
+            if "KO" in build_pkg[pkg]:
+               build_dep_ko.append(pkg) 
+        if build_dep_ko:
+              # the product is not installed : display message and return error status
+              msg="Please install them with %s before compiling salome" % check_cmd[0]
+              print("\nmissing compile time dependencies : ")
+              for md in build_dep_ko: 
+                  print(md)
+              print(msg)
+              return False
+        else:
+            return True    
+
     install_dir = product_info.install_dir
-    if ( (src.appli_test_property(config,"single_install_dir", "yes") and 
-          src.product.product_test_property(product_info,"single_install_dir", "yes")) or
-         (src.appli_test_property(config,"pip", "yes") and 
-          src.product.product_test_property(product_info,"pip", "yes") and
-          src.appli_test_property(config,"pip_install_dir", "python") ) ):
-        # if the product is installed in the single install dir, or in python (for pip managed products)
-        # we check the product file in state of the install directory.
-        filename = CONFIG_FILENAME + product_info.name + ".pyconf"
-        if not os.path.exists(os.path.join(install_dir, filename)): 
+    if src.product.product_is_fixed(product_info):
+        # we check directly the install dir only for fixed products
+        # (there is no pyconf file in that case)
+        if not os.path.exists(install_dir):
             return False
     else:
-        if not os.path.exists(install_dir):
+        filename = CONFIG_FILENAME + product_info.name + ".pyconf"
+        if not os.path.exists(os.path.join(install_dir, filename)): 
             return False
 
     # check extra files if specified in present_files.install section
@@ -889,6 +959,18 @@ def product_is_salome(product_info):
             "is_SALOME_module" in product_info.properties and
             product_info.properties.is_SALOME_module == "yes")
 
+def product_is_configuration(product_info):
+    """Know if a product is a configuration module
+    
+    :param product_info Config: The configuration specific to 
+                               the product
+    :return: True if the product is a configuration module, else False
+    :rtype: boolean
+    """
+    return ("properties" in product_info and
+            "configure_dependency" in product_info.properties and
+            product_info.properties.configure_dependency == "yes")
+
 def product_is_fixed(product_info):
     """Know if a product is fixed
     
@@ -1064,6 +1146,17 @@ def product_has_patches(product_info):
     res = ( "patches" in product_info and len(product_info.patches) > 0 )
     return res
 
+def product_has_post_script(product_info):
+    """Know if a product has a post install script
+    
+    :param product_info Config: The configuration specific to 
+                               the product
+    :return: True if the product has one or more patches
+    :rtype: boolean
+    """   
+    res = ( "post_script" in product_info and len(product_info.post_script) > 0 and not src.architecture.is_windows())
+    return res
+
 def product_has_logo(product_info):
     """Know if a product has a logo (YACSGEN generate)
     
@@ -1152,6 +1245,19 @@ def product_is_compile_time(product_info):
             "compile_time" in product_info.properties and
             product_info.properties.compile_time == "yes")
 
+def product_is_compile_and_runtime(product_info):
+    """Know if a product is only used at compile time
+    
+    :param product_info Config: The configuration specific to 
+                               the product
+    :return: True if the product is only used at compile time
+    :rtype: boolean
+    """
+    return ("properties" in product_info and
+            "compile_and_runtime" in product_info.properties and
+            product_info.properties.compile_and_runtime == "yes")
+
+
 
 def product_test_property(product_info, property_name, property_value):
     """Generic function to test if a product has a property set to a value
@@ -1173,6 +1279,55 @@ def product_test_property(product_info, property_name, property_value):
     result = eval(eval_expression)
     return result
 
+def check_system_dep(distrib, check_cmd, product_info):
+    """Search for system dependencies, check if installed
+    :param dist : The linux ditribution (CO7,DB10...)
+    :param check_cmd Config: The command to use for checking (rpm/apt)
+    :param product_info Config: The configuration specific to the product
+    :rtype: two dictionnaries for runtime and compile time dependencies with text status
+    """
+    runtime_dep={}
+    build_dep={}
+
+    if "system_info" in product_info:
+
+        sysinfo=product_info.system_info
+        additional_sysinfo = None
+
+        for key in sysinfo :
+            if distrib in key :
+                additional_sysinfo = sysinfo[key]
+
+        if check_cmd[0]=="rpm":
+            if "rpm" in sysinfo:
+                for pkg in sysinfo.rpm:
+                    runtime_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
+            if "rpm_dev" in sysinfo:
+                for pkg in sysinfo.rpm_dev:
+                    build_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
+            if additional_sysinfo :
+                if "rpm" in additional_sysinfo:
+                    for pkg in additional_sysinfo.rpm:
+                        runtime_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
+                if "rpm_dev" in additional_sysinfo:
+                    for pkg in additional_sysinfo.rpm_dev:
+                        build_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
+        if check_cmd[0]=="apt":
+            if "apt" in sysinfo:
+                for pkg in sysinfo.apt:
+                    runtime_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
+            if "apt_dev" in sysinfo:
+                for pkg in sysinfo.apt_dev:
+                    build_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
+            if additional_sysinfo :
+                if "apt" in additional_sysinfo:
+                    for pkg in additional_sysinfo.apt:
+                        runtime_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
+                if "apt_dev" in additional_sysinfo:
+                    for pkg in additional_sysinfo.apt_dev:
+                        build_dep[pkg]=src.system.check_system_pkg(check_cmd,pkg)
+
+    return runtime_dep,build_dep
 
 
 def get_product_components(product_info):