[TIC-CORE] support local repository
[archive/20170607/tools/tic-core.git] / tic / dependency.py
index 5698375..78b8232 100644 (file)
 # Contributors:
 # - S-Core Co., Ltd
 
-import rpm
 import logging
 from lxml import etree
 from tic.utils.error import TICError
+from tic.utils.rpmmisc import meetRequireVersion, compare_ver
+from tic.utils.rpmmisc import Dependency
+from tic.config import configmgr
+
+DEFAULT_PROFILE = 'EMPTY'
 
 def analyze_dependency(pkg_group):
     
     def dep_dfs(pkg_id):
-        logger = logging.getLogger(__name__)
+        #logger = logging.getLogger(__name__)
         if pkg_list[pkg_id].get('dependency') is not None:
             return pkg_list[pkg_id].get('dependency')
         
@@ -95,38 +99,33 @@ def analyze_dependency(pkg_group):
 def get_installed_packages(recipe, repoinfo, pkg_group):
     logger = logging.getLogger(__name__)
     
-    def _compare_ver(ver1, ver2):
-        return rpm.labelCompare((ver1.get('epoch'), ver1.get('ver'), ver1.get('rel')), 
-                                (ver2.get('epoch'), ver2.get('ver'), ver2.get('rel')))
-        
-    def _compare_req_cap_ver(req, cap):
-        epoch = cap.get('epoch')
-        ver = cap.get('ver')
-        rel = cap.get('rel')
-        if not req.get('epoch'): epoch = None
-        if not req.get('rel'): rel = None
-        return rpm.labelCompare((req.get('epoch'), req.get('ver'), req.get('rel')), (epoch, ver, rel))
-    
-    def _meetRequireVersion(req_ver, cmp_ver):
-        cmp_ret = _compare_req_cap_ver(req_ver, cmp_ver)
-        if cmp_ret == 0 and (req_ver['flags'] == 'EQ' or req_ver['flags'] == 'GE' or req_ver['flags'] == 'LE'):
-            return True
-        elif cmp_ret == 1 and (req_ver['flags'] == 'LT' or req_ver['flags'] == 'LE'):
-            return True
-        elif cmp_ret == -1 and (req_ver['flags'] == 'GT' or req_ver['flags'] == 'GE'):
-            return True
-        return False
-    
-    def _select_rpm(capability, require):
-        # TODO: temporary code (to support efl-data capability)
-        if len(capability) == 1:
-            return pkg_dict.get(capability[0].get('name'))
-        
+    def _select_rpm_from_files(fileList, require):
+        if not fileList or not require:
+            return None
+        # 1. sort
+        fileList.sort()
+        # 2-1. Choose the default rpm or the selected rpm
+        for fname in fileList:
+            file_info = pkg_dict.get(fname)
+            if fname in pkg_set or selected[file_info['id']] >= 1:
+                return file_info
+        # 2-2. Choose the rpm (it does not conflitcs)
+        for fname in fileList:
+            file_info = pkg_dict.get(fname)
+            if not _check_conflicts(file_info):
+                return file_info
+        return pkg_dict.get(fileList[0])
+
+    def _select_rpm(capability, require, recommends=None):
         provide_list = []
         # 1. Choose the rpm included in version from provides
-        if require.get('ver') is not None:
+        if require.get('ver'):
             for provide in capability:
-                if _meetRequireVersion(require, provide.get('data')):
+                ver_data = provide['data']
+                # If there is no capability version, use version of package
+                if not ver_data.get('ver'):
+                    ver_data = pkg_dict.get(provide['name']).get('version')
+                if meetRequireVersion(require, ver_data):
                     provide_list.append(provide)
         else:
             provide_list = capability
@@ -139,28 +138,48 @@ def get_installed_packages(recipe, repoinfo, pkg_group):
             return pkg_dict.get(provide_list[0].get('name'))
         
         # 2 Select one of the rpms by priority
-        # 2-1. Choose the default rpm or the selected rpm 
-        # TODO: default profile rpm should be selected
-        for i in range(0, len(provide_list)):
-            tmp_info = pkg_dict.get(provide_list[i].get('name'))
-            if tmp_info['name'] in pkg_set or selected[tmp_info['id']] >= 1:
-                return tmp_info
-        # 2-2. Select the latest version of rpm
+        # 2-1. Choose the default rpm or the selected rpm        
+        for pro in provide_list:
+            provide_info = pkg_dict.get(pro.get('name'))
+            if provide_info['name'] in pkg_set or selected[provide_info['id']] >= 1:
+                return provide_info
+
+        # 2-2. Choose the defualt profile
+        # TODO: should be supported
+        # if provide_info['profile'] == DEFAULT_PROFILE:
+            # return provide_info
+
+        # 2.3. Choose recommends pkg (pkg-name or capability)
+        if recommends:
+            for pro in provide_list:
+                provide_info = pkg_dict.get(pro.get('name'))
+                for reco in recommends:
+                    # 2-3 Case. select a pkg that is named
+                    if reco['name'] == provide_info['name']:
+                        return provide_info
+                    # 2-3 Case. select a pkg that provides
+                    for cap in provide_info.get('provides'):
+                        if reco['name'] == cap['name']:
+                            return provide_info
+        # 2-4. Select the latest version of rpm
         max_ver = None
-        for i in range(0, len(provide_list)):
-            if not _check_conflicts(pkg_dict.get(provide_list[i]['name'])):
+        for pro in provide_list:
+            if not _check_conflicts(pkg_dict.get(pro.get('name'))):
                 if max_ver:
-                    cap_info = provide_list[i].get('data')
-                    ret = _compare_ver(max_ver.get('data'), cap_info)
-                    # cap_info is greater than max_ver
-                    if ret == -1:
-                        max_ver = provide_list[i]
+                    cap_info = pro.get('data')
+                    ret = compare_ver(max_ver.get('data'), cap_info)
+                    if ret == 0: # equals
+                        # string compare (compare string lexicographically using ASCII value)
+                        if max_ver.get('name') > pro.get('name'):
+                            max_ver = pro
+                    elif ret == -1: # greater than max_ver
+                        max_ver = pro
                 else:
-                    max_ver = provide_list[i]
+                    max_ver = pro
         
         # all of capability pkg are in conflict
         if max_ver is None:
-            return pkg_dict.get(provide_list[i]['name'])
+            return pkg_dict.get(provide_list[0]['name'])
             
         return pkg_dict.get(max_ver.get('name'))
     
@@ -208,8 +227,9 @@ def get_installed_packages(recipe, repoinfo, pkg_group):
             for pro in pkg_info['provides']:
                 if pro['name'] in conflicts:
                     for con in conflicts[pro['name']]:
-                        if not con['data'].get('ver') or _meetRequireVersion(con['data'], pro):
+                        if not con['data'].get('ver') or meetRequireVersion(con['data'], pro):
                             #package conflict
+                            logger.info('Conflict %s and %s' % (pkg_info['name'], con['name']))
                             return True
         
         #2. If the conflict package defined by node is installed
@@ -219,12 +239,14 @@ def get_installed_packages(recipe, repoinfo, pkg_group):
                     for pro in provides[con['name']]:
                         pkg = pkg_dict[pro['name']]
                         if selected[pkg['id']] != 0:
-                            if not con.get('ver') or _meetRequireVersion(con, pkg['version']):
+                            if not con.get('ver') or meetRequireVersion(con, pkg['version']):
+                                logger.info('Conflict %s and %s' % (pkg_info['name'], pkg['name']))
                                 return True
                 elif con['name'] in pkg_dict:
                     pkg = pkg_dict[con['name']]
                     if selected[pkg['id']] != 0:
-                        if not con.get('ver') or _meetRequireVersion(con, pkg['version']):
+                        if not con.get('ver') or meetRequireVersion(con, pkg['version']):
+                            logger.info('Conflict %s and %s' % (pkg_info['name'], pkg['name']))
                             return True
         return False
     
@@ -258,7 +280,7 @@ def get_installed_packages(recipe, repoinfo, pkg_group):
         _add_conflicts(pkg_info)
         
         # Installation dependency analysis of rpm
-        for dep_tag in ['requires']: # 'recommends'
+        for dep_tag in [Dependency.REQUIRES, Dependency.RECOMMENDS]:
             if pkg_info.get(dep_tag):
                 for req in pkg_info.get(dep_tag):
                     choose = None
@@ -266,11 +288,21 @@ def get_installed_packages(recipe, repoinfo, pkg_group):
                     if req['name'] in provides:
                         # capability : [provide_rpm_1, provide_rpm_2, ... ] 
                         # Select the rpm that meets the condition (version)
-                        choose = _select_rpm(provides[req['name']], req)
+                        if dep_tag == Dependency.REQUIRES:
+                            choose = _select_rpm(provides[req['name']], req, pkg_info.get('recommends'))
+                        else:
+                            choose = _select_rpm(provides[req['name']], req)
                     elif req['name'] in files:
-                        choose = pkg_dict.get(files[req['name']][0])
+                        choose = _select_rpm_from_files(files[req['name']], req)
+                        #choose = pkg_dict.get(files[req['name']][0])
                     elif req['name'] in pkg_dict:
                         choose = pkg_dict.get(req['name'])
+                    
+                    if dep_tag == Dependency.RECOMMENDS:
+                        # A Recommends B: B is installed when A is installed and B has no conflicts.
+                        if not choose or _check_conflicts(choose):
+                            logger.info('%s recommended by %s is ignored for selection (Conflict)' % (req['name'], pkg_info['name']))
+                            continue
                         
                     if choose:
                         if selected[choose['id']] == 0:
@@ -288,7 +320,7 @@ def get_installed_packages(recipe, repoinfo, pkg_group):
                         _create_reference(pkg_info, choose)
                     else:
                         # the rpm does not exists
-                        logger.info('the capability(%s) needed by %s does not exist. should be checked for error' % (req['name'], pkg_info['name']))
+                        logger.info(configmgr.message['dependency_not_exist'] % (req['name'], pkg_info['name']))
                         progress['status'] = False
                         break                     
             
@@ -367,9 +399,9 @@ def get_installed_packages(recipe, repoinfo, pkg_group):
                 
                 # If pkg has a circular dependency and is unchekcable,
                 # circular dep. pkgs can only be visited once
-                if fnode.get('group') and group_visited[fnode['group']][fnode['name']] == 1:
+                gvisite = group_visited.get(fnode.get('group'))
+                if gvisite and gvisite[fnode['name']] == 1:
                     continue
-                
                 _remove_reference(node, fnode)
             node['forward'] = None
             node['group'] = None
@@ -393,24 +425,29 @@ def get_installed_packages(recipe, repoinfo, pkg_group):
             group_set.update(g.get('Groups'))
         if g.has_key('ExtraPackages'):
             pkg_set.update(g.get('ExtraPackages'))
-    group_dict = dict.fromkeys(group_set)
+    #group_dict = dict.fromkeys(group_set)
     
     # parsing group.xml
-    try:
-        tree = etree.parse(repoinfo[0].get('comps'))
-        root = tree.getroot()
-    except etree.XMLSyntaxError as e:
-        raise TICError('primary.xml syntax error. %s', e)
-    
-    # Convert groups to packages
-    for elm in root.findall('group'):
-        group_name = elm.find('name').text
-        if group_dict.has_key(group_name):
-            pkglist = elm.find('packagelist')
-            plist = []
-            for pkgreq in pkglist.findall('packagereq'):                
-                plist.append(pkgreq.text)
-            pkg_set.update(set(plist))
+    if group_set:
+        for repo in repoinfo:
+            if repo.get('comps'):
+                try:
+                    tree = etree.parse(repo.get('comps'))
+                    root = tree.getroot()
+                except etree.XMLSyntaxError as e:
+                    logger.info(e)
+                    raise TICError(configmgr.message['xml_parse_error'] % ('group.xml', repo['baseurl']))
+                
+                # Convert groups to packages
+                for elm in root.findall('group'):
+                    group_name = elm.find('name').text
+                    if group_name in group_set:
+                        pkglist = elm.find('packagelist')
+                        plist = []
+                        for pkgreq in pkglist.findall('packagereq'):
+                            plist.append(pkgreq.text)
+                        pkg_set.update(set(plist))
+                        group_set.discard(group_name);
     
     pkg_dict = pkg_group.get('pkg_dict')
     provides = pkg_group.get('provides')
@@ -433,11 +470,14 @@ def get_installed_packages(recipe, repoinfo, pkg_group):
         progress['status'] = True
         pkg_info = pkg_dict.get(pkg_name)
         
-        # TODO: temporary code (Define capability in group)
         if not pkg_info:
-            pro = provides.get(pkg_name)[0]
-            pkg_info = pkg_dict.get(pro['name'])
-            
+            if provides.get(pkg_name):
+                pro = provides.get(pkg_name)[0]
+                pkg_info = pkg_dict.get(pro['name'])
+            else:
+                logger.info(configmgr.message['package_not_exist'] % pkg_name)
+                continue 
+
         pkg_info['selfChecked'] = True
         if selected[pkg_info['id']] == 0:
             dep_set = _analyze_dep(pkg_info)
@@ -447,4 +487,4 @@ def get_installed_packages(recipe, repoinfo, pkg_group):
                 # delete forward/backward reference
                 group_visited = {} 
                 _remove_reference(None, pkg_info)
-    return list(install_rpm)
\ No newline at end of file
+    return list(install_rpm)