[TIC-Web] fix the issue that cannot analyze install-dependency 46/126346/3
authorChulwoo Shin <cw1.shin@samsung.com>
Fri, 21 Apr 2017 04:56:46 +0000 (13:56 +0900)
committerChulwoo Shin <cw1.shin@samsung.com>
Fri, 21 Apr 2017 05:05:09 +0000 (14:05 +0900)
- fix the issue that cannot analyze install-dependency
- support the recommends tag

Change-Id: I456e83a7195605961423c0b378841d6024c586b6
Signed-off-by: Chulwoo Shin <cw1.shin@samsung.com>
public/src/js/page/package.js

index 2f72976..dd2a856 100644 (file)
@@ -40,14 +40,16 @@ define([
 
     var $tree = $('#tic-package-left-col-tree');
     var defaultFilters = [];
-    var repos = [];
-    var packages = [];
-    var pkgInfo = null;
-    var provides = null;
-    var files = null;
-    var conflicts = null;
-    var groups = null;
-    var groupId = 0;
+    let repos = [];
+    let packages = [];
+    let pkgInfo = null;
+    let provides = null;
+    let files = null;
+    let groups = null;
+    let groupId = 0;
+    let conflicts = null;
+    let selfCheckedMap = {};
+    let requiredInstPkgs = null;
 
     //To manage that the image creation
     var micMgr = [];
@@ -218,10 +220,13 @@ define([
         }
     }
 
-    function _setDefaultPackage(defaultPackages) {
+    function _setDefaultPackage(installPackages) {
         var nodes = []
-        var pickDefault = _.pick(pkgInfo, defaultPackages);
+        var pickDefault = _.pick(pkgInfo, installPackages);
         _.forEach(pickDefault, function(value, key) {
+            if (value.selfChecked === true) {
+                selfCheckedMap[key] = value;
+            }
             // One or more of the same nodes can exist
             _.forEach(value.view, function(node) {
                 nodes.push(node);
@@ -355,267 +360,494 @@ define([
         }
     }
 
-    // analyze install-dependency (requires)
-    function _analyzeInstallDependency(checkNode) {
-        var stack = [];
-        var pkg_count = Object.keys(pkgInfo).length
-        var number = 0;
-        var sccNum = 0;
-        var groupNum = 0;
-        var selected = new Array(pkg_count);
-        var scc_id = new Array(pkg_count);
-        var min_num = new Array(pkg_count);
-        var progressStatus = true;
-        var errorMsg = null;
-        var conflictsTable = {};
-
-        // init check type
-        _.forEach(pkgInfo, function(value, key) {
-            // check type: -1(unchecked), 0(checked), 1~N(checking)
-            selected[value.id] = value.checked ? 0 : -1;
-            scc_id[value.id] = 0;
-        });
-
-        function _makeSCC(pkgId) {
-            sccNum += 1;
-            var sccList = [];
-
-            // make scc
-            while(!_.isEmpty(stack)) {
-                var pkg = stack.pop();
-                scc_id[pkg.id] = sccNum;
-                sccList.push(pkg);
-
-                if (pkg.id === pkgId) {
-                    break;
+    function _checkConflicts(node, selected) {
+        var ret = {'result': false, 'message': null, 'conflictPkg': null};
+        // 1. Check whether node can be installed
+        if (!_.isEmpty(node.provides)) {
+            _.forEach(node.provides, function(provide) {
+                if (!_.isEmpty(conflicts[provide.name])) {
+                    _.forEach(conflicts[provide.name], function(conflict) {
+                        // compare version between conflict with provide
+                        if (!conflict.data.ver || _meetRequiredVersion(conflict.data, provide)) {
+                            // package conflict
+                            logger.info('[Conflict] The "' + node.name + '" is conflict with "' + conflict.name + '"');
+                            ret.message = 'The "' + node.name + '" is conflict with "' + conflict.name + '"';
+                            ret.result = true;
+                            ret.conflictPkg = pkgInfo[conflict.name];
+                            return false;
+                        }
+                    });
                 }
-            }
-
-            // circular depependency
-            if (sccList.length > 1) {
-                groupId += 1;
-                var groupPkgList = [];
-                _.forEach(sccList, function(pkg) {
-                    pkg.group = groupId;
-                    groupPkgList.push(pkg.name);
-                });
-                groups[groupId] = groupPkgList;
-                // logger.info('circular dependency: groupId(' + groupId + '), list(' + groupPkgList + ')');
-            }
+                if (ret.result === true) {
+                    return false;
+                }
+            });
         }
 
-        function _createReference(node1, node2) {
-            if (!_.isEmpty(node1.forward) && _.includes(node1.forward, node2.name))
-                return
+        // 2. If the conflict package defined by node is installed
+        if (ret.result === false && !_.isEmpty(node.conflicts)) {
+            _.forEach(node.conflicts, function(conflict) {
+                if (conflict.name in provides) {
+                    _.forEach(provides[conflict.name], function(provide) {
+                        pkg = pkgInfo[provide.name];
+                        if (selected[pkg.id] !== -1) {
+                            if (!conflict.ver || _meetRequiredVersion(conflict, provide.data)) {
+                                // package conflicts
+                                logger.info('[Conflict] The "' + provide.name + '" is conflict with "' + node.name + '"');
+                                ret.message = 'The "'+provide.name+'" is conflict with "'+node.name + '"';
+                                ret.result = true;
+                                ret.conflictPkg = pkg;
+                                return false;
+                            }
+                        }
+                    });
+                } else if (conflict.name in pkgInfo) {
+                    var pkg = pkgInfo[conflict.name];
+                    if (selected[pkg.id] !== -1) {
+                        if (!conflict.ver || _meetRequiredVersion(conflict, pkg.version)) {
+                            // package conflicts
+                            logger.info('[Conflict] The "' + pkg.name + '" is conflit with "' + node.name + '"');
+                            ret.message = 'The "' + pkg.name +'" is conflict with "' + node.name + '"';
+                            ret.result = true;
+                            ret.conflictPkg = pkg
+                            return false;
+                        }
+                    }
+                }
+                if (ret.result === true) {
+                    return false;
+                }
+            });
+        }
+        return ret;
+    }
 
-            if (!_.isEmpty(node1.forward)) {
-                node1.forward.push(node2.name);
-            } else {
-                node1.forward = [node2.name];
+    function _select_files(require, selected) {
+        if (_.isEmpty(require) || _.isEmpty(files[require.name])) {
+            return null;
+        }
+        var selectpkg = null;
+        // 1. sort in ascending order
+        var fileList = files[require.name];
+        fileList.sort();
+        // 2-1. Choose the selected rpm
+        for (let fname of fileList) {
+            let fileInfo = pkgInfo[fname];
+            if ((fname in requiredInstPkgs) || selected[fileInfo.id] !== -1) {
+                return fileInfo;
             }
-
-            if (!_.isEmpty(node2.backward)) {
-                node2.backward.push(node1.name);
-            } else {
-                node2.backward = [node1.name];
+        }
+        // 2-2. Choose the rpm (it does not conflitcs)
+        for (let fname of fileList) {
+            let fileInfo = pkgInfo[fname];
+            var checkConflict = _checkConflicts(fileInfo, selected);
+            if (checkConflict.result === false) {
+                return fileInfo;
             }
         }
+        return pkgInfo[fileList[0]];
+    }
 
-        function _select_files(fileList, require) {
-            if (!fileList || !require){
-                return null;
-            }
-            var selectpkg = null;
-            // 1. sort in ascending order
-            fileList.sort();
-            // 2-1. Choose the selected rpm
-            for (let fname of fileList) {
-                let fileInfo = pkgInfo[fname];
-                if (fileInfo.checked || selected[fileInfo.id] >= 1) {
-                    return fileInfo;
+    function _select_rpm(require, selected, recommend) {
+        if (_.isEmpty(require) || _.isEmpty(provides[require.name])) {
+            return null;
+        }
+        var capability = provides[require.name];
+        var provideList = [];
+        // 1. Choose the rpm included in version from provides
+        if (require.ver) {
+            _.forEach(capability, function(provide) {
+                var verData = provide.data;
+                if(!provide.data.ver) {
+                    var pkg = pkgInfo[provide.name];
+                    verData = pkg.version;
                 }
-            }
-            // 2-2. Choose the rpm (it does not conflitcs)
-            for (let fname of fileList) {
-                let fileInfo = pkgInfo[fname];
-                var checkConflict = _checkConflicts(fileInfo);
-                if (checkConflict.result === false) {
-                    return fileInfo;
+                if (_meetRequiredVersion(require, verData)) {
+                    // Meet the required version
+                    provideList.push(provide);
                 }
-            }
-            return pkgInfo[fileList[0]];
-        }
-
-        function _select_rpm(capability, require, recommend) {
-            var provideList = [];
-            // 1. Choose the rpm included in version from provides
-            if (require.ver) {
-                _.forEach(capability, function(provide) {
-                    var verData = provide.data;
-                    if(!provide.data.ver) {
-                        var pkg = pkgInfo[provide.name];
-                        verData = pkg.version;
-                    }
-                    if (_meetRequiredVersion(require, verData)) {
-                        // Meet the required version
-                        provideList.push(provide);
-                    }
-                });
-            } else {
-                provideList = capability;
-            }
+            });
+        } else {
+            provideList = capability;
+        }
 
-            // error case (the rpm does not exist)
-            if (_.isEmpty(provideList)) {
-                return null;
-            }
+        // error case (the rpm does not exist)
+        if (_.isEmpty(provideList)) {
+            return null;
+        }
 
-            if (provideList.length === 1) {
-                return pkgInfo[provideList[0].name];
-            }
+        if (provideList.length === 1) {
+            return pkgInfo[provideList[0].name];
+        }
 
-            // 2. Select one of the rpms by priority
-            // 2-1. Choose the default rpm or the selected rpm
-            for (var provide of provideList) {
-                var tmpInfo = pkgInfo[provide.name];
-                if (tmpInfo.checked || selected[tmpInfo.id] >= 1) {
-                    return tmpInfo;
-                }
+        // 2. Select one of the rpms by priority
+        // 2-1. Choose the default rpm or the selected rpm
+        for (var provide of provideList) {
+            var candidatePkg = pkgInfo[provide.name];
+            if ((provide.name in requiredInstPkgs) || selected[candidatePkg.id] !== -1) {
+                return candidatePkg;
             }
-            // 2-2. Choose the rpm of default profile
-            // TODO: should be supported
-
-            // 2-3. Choose recommends pkg (pkg-name or capability)
-            if (recommend) {
-                for (let provide of provideList) {
-                    let provide_info = pkgInfo[provide.name];
-                    for (var reco of recommend) {
-                        // 2-3-1. select a pkg that is named
-                        if (reco.name == provide_info.name) {
+        }
+        // 2-2. Choose the rpm of default profile
+        // TODO: should be supported
+
+        // 2-3. Choose recommends pkg (pkg-name or capability)
+        if (recommend) {
+            for (let provide of provideList) {
+                let provide_info = pkgInfo[provide.name];
+                for (var reco of recommend) {
+                    // 2-3-1. select a pkg that is named
+                    if (reco.name == provide_info.name) {
+                        return provide_info;
+                    }
+                    // 2-3-2. select a pkg that provides
+                    for (var cap of provide_info['provides']) {
+                        if (reco.name == cap.name) {
                             return provide_info;
                         }
-                        // 2-3-2. select a pkg that provides
-                        for (var cap of provide_info['provides']) {
-                            if (reco.name == cap.name) {
-                                return provide_info;
-                            }
-                        }
                     }
                 }
             }
-            var maxVersion = null;
-            // 2-4. Select the latest version of rpm
-            _.forEach (provideList, function(provide) {
-                var checkConflict = _checkConflicts(pkgInfo[provide.name]);
-                if (checkConflict.result === false) {
-                    if (maxVersion) {
-                        var ret = _comapre_ver(maxVersion.data, provide.data)
-                        // version equals
-                        if (ret === 0) {
-                            // string compare
-                            if (maxVersion.name > provide.name) {
-                                maxVersion = provide;
-                            }
-                        } else if (ret === -1) {
-                            // greater than maxVersion
+        }
+        var maxVersion = null;
+        // 2-4. Select the latest version of rpm
+        _.forEach (provideList, function(provide) {
+            var checkConflict = _checkConflicts(pkgInfo[provide.name], selected);
+            if (checkConflict.result === false) {
+                if (maxVersion) {
+                    var ret = _comapre_ver(maxVersion.data, provide.data)
+                    // version equals
+                    if (ret === 0) {
+                        // string compare
+                        if (maxVersion.name > provide.name) {
                             maxVersion = provide;
                         }
-                    } else {
+                    } else if (ret === -1) {
+                        // greater than maxVersion
                         maxVersion = provide;
                     }
+                } else {
+                    maxVersion = provide;
+                }
+            }
+        });
+
+        // all of capability pkg are in conflict
+        if (_.isEmpty(maxVersion)) {
+            return pkgInfo[provideList[0].name];
+        }
+
+        return pkgInfo[maxVersion.name];
+    }
+
+    function _addConflicts(node) {
+        if (!_.isEmpty(node.conflicts)) {
+            _.forEach(node.conflicts, function(conflict) {
+                // logger.info(node.name + ' add conflict rpm : ' + conflict.name);
+                if (_.isEmpty(conflicts[conflict.name])) {
+                    conflicts[conflict.name] = [];
+                    conflicts[conflict.name].push({'name':node.name, 'data':conflict});
+                } else {
+                    conflicts[conflict.name].push({'name':node.name, 'data':conflict});
                 }
             });
+        }
+    }
 
-            // all of capability pkg are in conflict
-            if (_.isEmpty(maxVersion)) {
-                return pkgInfo[provideList[0].name];
+    function _deleteConflictData(node) {
+        if (!_.isEmpty(node.conflicts)) {
+            _.forEach(node.conflicts, function(conflict) {
+                if (!_.isEmpty(conflicts[conflict.name])) {
+                    var cList = conflicts[conflict.name];
+                    for(var i=0; i<cList.length; i++) {
+                        if (cList[i].name === node.name) {
+                            cList.splice(i, 1);
+                            break;
+                        }
+                    }
+                }
+            });
+        }
+    }
+
+    function _checkDependencyValidation(checkNode) {
+        let pkg_count = Object.keys(pkgInfo).length;
+        let number = 0;
+        let selected = Array.apply(null, Array(pkg_count)).map(Number.prototype.valueOf, -1);
+        let progressStatus = true;
+        let errorMsg = null;
+        let comp_rpms = {};
+        let capabilities = {};
+        let requiresRefer = {};
+
+        function _remove_dep_rpm(selectList) {
+            if (_.isEmpty(selectList)) {
+                return;
             }
+            _.forEach(selectList, function(rpmInfo) {
+                selected[rpmInfo.id] = 0;
+                _deleteConflictData(rpmInfo);
+            });
+        }
 
-            return pkgInfo[maxVersion.name];
-        }
-
-        function _checkConflicts(node) {
-            var ret = {'result': false, 'message': null};
-            // 1. Check whether node can be installed
-            if (!_.isEmpty(node.provides)) {
-                _.forEach(node.provides, function(provide) {
-                    if (!_.isEmpty(conflicts[provide.name])) {
-                        _.forEach(conflicts[provide.name], function(conflict) {
-                            // compare version between conflict with provide
-                            if (!conflict.data.ver || _meetRequiredVersion(conflict.data, provide)) {
-                                // package conflict
-                                logger.info('[Conflict] The ' + node.name + ' conflict with ' + conflict.name);
-                                ret.message = 'The ' + node.name + ' conflict with ' + conflict.name;
-                                ret.result = true;
+        function _addCapabilities(node) {
+            if (_.isEmpty(node.provides)) {
+                return;
+            }
+            _.forEach(node.provides, function(pro){
+                if (!(pro.name in capabilities)) {
+                    capabilities[pro.name] = [];
+                }
+                capabilities[pro.name].push(node);
+            });
+        }
+
+        function _addReference(node, require) {
+            if (!(node.name in requiresRefer)) {
+                requiresRefer[node.name] = {};
+            }
+            requiresRefer[node.name][require.name] = 1;
+        }
+
+        function _isCompatablePkg(originPkg, newPkg) {
+            let matched = true;
+            if (originPkg.name in requiresRefer) {
+                _.forEach(requiresRefer[originPkg.name], function(value, rname){
+                    matched = false;
+                    if (!_.isEmpty(newPkg['provides'])) {
+                        _.forEach(newPkg['provides'], function(pro){
+                            if (rname === pro.name) {
+                                matched = true;
                                 return false;
                             }
                         });
                     }
-                    if (ret.result === true) {
+                    if (!matched && !_.isEmpty(newPkg['file'])) {
+                        _.forEach(newPkg['file'], function(fname){
+                            if (rname === fname) {
+                                matched = true;
+                                return false;
+                            }
+                        });
+                    }
+                    if(matched === false) {
+                        // newPkg not compatible
                         return false;
                     }
                 });
             }
-            if (ret.result === false) {
-                if (!_.isEmpty(conflicts[node.name])) {
-                    _.forEach(conflicts[node.name], function(conflict) {
-                        if (!conflict.data.ver || _meetRequiredVersion(conflict.data, node.version)) {
-                            // package conflict
-                            logger.info('[Conflict] The ' + node.name + ' conflict with ' + conflict.name);
-                            ret.message = 'The ' + node.name + ' conflict with ' + conflict.name;
-                            ret.result = true;
-                            return false;
-                        }
-                    });
+            return matched;
+        }
+
+        function _checkDependency(node, selectList) {
+            let pkgId = node.id;
+            number += 1;
+            selected[pkgId] = number;
+            selectList.push(node);
+
+            // add rpm's capabilities
+            _addCapabilities(node);
+
+            // Check for conflicts
+            let checkCon = _checkConflicts(node, selected)
+            if (checkCon.result === true) {
+                if (node.selfChecked === false && _isCompatablePkg(checkCon.conflictPkg, node)) {
+                    logger.info('[Compatible_1] "' + node.name + '" and "' + checkCon.conflictPkg.name + '" is compatible');
+                    comp_rpms[node.name] = node;
+                } else {
+                    errorMsg = checkCon.message;
+                    progressStatus = false;
+                    return false;
                 }
+            } else if (!_.isEmpty(node.provides)) {
+                _.forEach(node.provides, function(pro) {
+                    if (pro.name in capabilities) {
+                        _.forEach(capabilities[pro.name], function(c_info) {
+                            if (c_info['id'] === node['id']) {
+                                return; // continue
+                            }
+                            if (c_info.selfChecked === false && _isCompatablePkg(c_info, node)) {
+                                logger.info('[Compatible_2] "' + node.name + '" and "' + c_info.name + '" is compatible');
+                                comp_rpms[node.name] = node;
+                            }
+                        });
+                    }
+                });
             }
 
-            // 2. If the conflict package defined by node is installed
-            if (ret.result === false && !_.isEmpty(node.conflicts)) {
-                _.forEach(node.conflicts, function(conflict) {
-                    if (_.has(provides, conflict.name)) {
-                        _.forEach(provides[conflict.name], function(provide) {
-                            if (pkgInfo[provide.name].checked === true) {
-                                if (!conflict.ver || _meetRequiredVersion(conflict, provide.data)) {
-                                    // package conflicts
-                                    logger.info('[Conflict] The ' + node.name + ' conflict with ' + provide.name);
-                                    ret.message = 'The '+node.name+'(conflicts:'+conflict.name+') conflict with '+provide.name;
-                                    ret.result = true;
+            // add rpms into conflicts table.
+            _addConflicts(node);
+
+            // installation dependency analysis of package
+            _.forEach(['requires', 'recommends'], function(depTag) {
+                if (depTag in node) {
+                    _.forEach(node[depTag], function(require) {
+                        let choose = null;
+                        // self-reference (e.g. vim-base)
+                        if (require.name === node.name){
+                            return; // continue
+                        }
+                        // Find dependency rpm based on capability and files
+                        if (require.name in provides) {
+                            if (depTag == 'requires') {
+                                choose = _select_rpm(require, selected, node['recommends']);
+                            } else {
+                                choose = _select_rpm(require, selected, null);
+                            }
+                        } else if (require.name in files) {
+                            choose = _select_files(require, selected);
+                        } else if (require.name in pkgInfo) {
+                            choose = pkgInfo[require.name];
+                        }
+
+                        if (depTag === 'recommends') {
+                            if (!choose || (_checkConflicts(choose, selected)).result === true) {
+                                logger.info('"' + require.name + '" recommended by "' + node.name + '" is ignored for selection (Conflict)');
+                                return; // continue
+                            }
+                        }
+
+                        if (!_.isEmpty(choose)) {
+                            // add refer
+                            if (depTag === 'requires') {
+                                _addReference(choose, require);
+                            }
+
+                            if (selected[choose.id] === -1) {
+                                if (_checkDependency(choose, selectList) === false) {
                                     return false;
                                 }
                             }
-                        });
-                    } else if (_.has(pkgInfo, conflict.name)) {
-                        var pkg = pkgInfo[conflict.name];
-                        if (pkg.checked === true) {
-                            if (!conflict.ver || _meetRequiredVersion(conflict, pkg.version)) {
-                                // package conflicts
-                                logger.info('[Conflict] The ' + node.name + ' conflit with ' + pkgInfo.name);
-                                ret.message = 'The '+node.name+'(conflicts:'+conflict.name+') conflict with '+pkg.name;
-                                ret.result = true;
-                                return false;
-                            }
+                        } else {
+                            // the capability does not exist. Stop dependency analysis
+                            progressStatus = false;
+                            errorMsg = 'The "' + require.name + '" needed by "' + node.name + '" does not exist. Please check the repository';
+                            logger.info('###' + errorMsg);
+                            return false;
                         }
-                    }
-                    if (ret.result === true) {
-                        return false;
-                    }
-                });
+                    });
+                }
+                if (progressStatus === false) {
+                    // Stop dependency analysis
+                    return false;
+                }
+            });
+            return progressStatus;
+        }
+
+        // global variable (Reference object for rpm selection)
+        conflicts = {};
+        requiredInstPkgs = {};
+        _.forEach(selfCheckedMap, function(value, key){
+            requiredInstPkgs[key] = value;
+        });
+        let selectList;
+        _.forEach(selfCheckedMap, function(node, key) {
+            if (selected[node.id] === -1) {
+                selectList = [];
+                progressStatus = true;
+                if (_checkDependency(node, selectList)) {
+                    comp_rpms[node.name] = node;
+                } else {
+                    _remove_dep_rpm(selectList);
+                    logger.info('###ERROR### The "' + node.name + '" does not selected')
+                }
+            } else {
+                comp_rpms[node.name] = node;
+            }
+        });
+        selectList = [];
+        if (!_.isEmpty(checkNode)) {
+            if (_checkDependency(checkNode, selectList)) {
+                comp_rpms[checkNode.name] = checkNode;
             }
-            return ret;
         }
 
-        function _addConflicts(node) {
-            if (!_.isEmpty(node.conflicts)) {
-                _.forEach(node.conflicts, function(conflict) {
-                    // logger.info(node.name + ' add conflict rpm : ' + conflict.name);
-                    if (_.isEmpty(conflicts[conflict.name])) {
-                        conflicts[conflict.name] = [];
-                        conflicts[conflict.name].push({'name':node.name, 'data':conflict});
-                    } else {
-                        conflicts[conflict.name].push({'name':node.name, 'data':conflict});
-                    }
+        if (progressStatus === false) {
+            return {'result': false, 'message': errorMsg};
+        }
+        return {'result': true, 'data': comp_rpms};
+    }
+
+    function _resetRerferenceInfo() {
+        _.forEach(pkgInfo, function(value, key) {
+            value['backward'] = null;
+            value['forward'] = null;
+            value['group'] = null;
+        });
+    }
+
+    // analyze install-dependency (requires)
+    function _analyzeInstallDependency(checkNode, requiredPkgs) {
+        let stack = [];
+        let pkg_count = Object.keys(pkgInfo).length
+        let number = 0;
+        let sccNum = 0;
+        let groupNum = 0;
+        let selected = Array.apply(null, Array(pkg_count)).map(Number.prototype.valueOf, -1);
+        let scc_id = Array.apply(null, Array(pkg_count)).map(Number.prototype.valueOf, 0);
+        let min_num = new Array(pkg_count);
+        let progressStatus = true;
+        let errorMsg = null;
+        let conflictsTable = {};
+
+        // global variable (Reference object for rpm selection)
+        conflicts = {};
+        requiredInstPkgs = {};
+        _.forEach(requiredPkgs, function(value, key){
+            requiredInstPkgs[key] = value;
+        });
+        _resetRerferenceInfo();
+
+        // init check type
+        // _.forEach(pkgInfo, function(value, key) {
+        //     // check type: -1(unchecked), 0(checked), 1~N(checking)
+        //     selected[value.id] = value.checked ? 0 : -1;
+        //     scc_id[value.id] = 0;
+        // });
+
+        function _makeSCC(pkgId) {
+            sccNum += 1;
+            var sccList = [];
+
+            // make scc
+            while(!_.isEmpty(stack)) {
+                var pkg = stack.pop();
+                scc_id[pkg.id] = sccNum;
+                sccList.push(pkg);
+
+                if (pkg.id === pkgId) {
+                    break;
+                }
+            }
+
+            // circular depependency
+            if (sccList.length > 1) {
+                groupId += 1;
+                var groupPkgList = [];
+                _.forEach(sccList, function(pkg) {
+                    pkg.group = groupId;
+                    groupPkgList.push(pkg.name);
                 });
+                groups[groupId] = groupPkgList;
+                // logger.info('circular dependency: groupId(' + groupId + '), list(' + groupPkgList + ')');
+            }
+        }
+
+        function _createReference(node1, node2) {
+            if (!_.isEmpty(node1.forward) && _.includes(node1.forward, node2.name))
+                return
+
+            if (!_.isEmpty(node1.forward)) {
+                node1.forward.push(node2.name);
+            } else {
+                node1.forward = [node2.name];
+            }
+
+            if (!_.isEmpty(node2.backward)) {
+                node2.backward.push(node1.name);
+            } else {
+                node2.backward = [node1.name];
             }
         }
 
@@ -630,7 +862,7 @@ define([
             dependents[node.name] = node;
 
             // Check for conflicts
-            var checkConflict = _checkConflicts(node)
+            var checkConflict = _checkConflicts(node, selected);
             if (checkConflict.result === true) {
                 errorMsg = checkConflict.message;
                 progressStatus = false;
@@ -643,7 +875,7 @@ define([
 
             // installation dependency analysis of package
             _.forEach(['requires', 'recommends'], function(depTag) {
-                if (_.has(node, depTag)) {
+                if (depTag in node) {
                     _.forEach(node[depTag], function(require) {
                         var choose = null;
                         // self-reference (e.g. vim-base)
@@ -651,27 +883,29 @@ define([
                             return; // continue
                         }
                         // Find dependency rpm based on capability and files
-                        if (_.has(provides, require.name)) {
-                            var capList = provides[require.name];
+                        if (require.name in provides) {
                             if (depTag == 'requires') {
-                                choose = _select_rpm(capList, require, node['recommends']);
+                                choose = _select_rpm(require, selected, node['recommends']);
                             } else {
-                                choose = _select_rpm(capList, require);
+                                choose = _select_rpm(require, selected, null);
                             }
-                        } else if (_.has(files, require.name)) {
-                            choose = _select_files(files[require.name], require);
-                        } else if (_.has(pkgInfo, require.name)) {
+                        } else if (require.name in files) {
+                            choose = _select_files(require, selected);
+                        } else if (require.name in pkgInfo) {
                             choose = pkgInfo[require.name];
                         }
 
-                        if (depTag == 'recommends') {
-                            if (!choose || (_checkConflicts(choose)).result === true) {
-                                logger.info(require.name + ' recommended by ' + node.name + ' is ignored for selection (Conflict)');
+                        if (depTag === 'recommends') {
+                            if (!choose || (_checkConflicts(choose, selected)).result === true) {
+                                logger.info('"' + require.name + '" recommended by "' + node.name + '" is ignored for selection (Conflict)');
                                 return; // continue
                             }
                         }
 
                         if (choose) {
+                            // add forward/backward reference
+                            _createReference(node, choose);
+
                             if (selected[choose.id] === -1) {
                                 var result = _analyzeDep(choose);
                                 if (progressStatus === false) {
@@ -679,7 +913,7 @@ define([
                                     return false;
                                 }
                                 _.forEach(result, function(value, key) {
-                                     if (!_.has(dependents, key)) {
+                                     if (!(key in dependents)) {
                                          dependents[key] = value;
                                      }
                                 });
@@ -688,9 +922,6 @@ define([
                                 // cross edge that can not be ignored
                                 min_num[pkgId] = Math.min(min_num[pkgId], min_num[choose.id])
                             }
-
-                            // add forward/backward reference
-                            _createReference(node, choose);
                         } else {
                             // the capability does not exist. Stop dependency analysis
                             progressStatus = false;
@@ -714,7 +945,29 @@ define([
             return dependents;
         }
 
-        var analyzeRet = _analyzeDep(checkNode);
+        let analyzeRet = {};
+        if (!_.isEmpty(selfCheckedMap)) {
+            _.forEach(selfCheckedMap, function(value, key) {
+                if (selected[value.id] === -1) {
+                    let instPkg = _analyzeDep(value);
+                    if (progressStatus === true) {
+                        _.forEach(instPkg, function(value, key){
+                            analyzeRet[key] = value;
+                        });
+                    } else {
+                        // TODO: remove reference
+                    }
+                }
+            });
+        }
+        if (!_.isEmpty(checkNode)) {
+            let instPkg = _analyzeDep(checkNode);
+            if (progressStatus === true) {
+                _.forEach(instPkg, function(value, key){
+                    analyzeRet[key] = value;
+                });
+            }
+        }
         if (progressStatus === false) {
             return {'result': false, 'message': errorMsg};
         }
@@ -743,41 +996,25 @@ define([
                 for(var b=0; b<pkg.backward.length; b++) {
                     // If node is Referenced by another node (Not a node in the group),
                     // unable to uncheck group nodes
-                    if (!_.has(groupObj, pkg.backward[b])) {
+                    if (!(pkg.backward[b] in groupObj)) {
                         return false;
                     }
                 }
             }
             // init visited obj
-            groupVisited[groupId] = {};
+            // groupVisited[groupId] = {};
             // Delete backward reference of group node
-            _.forEach(groupPkgList, function(pkgName) {
-                var pkg = _find(pkgName);
-                // backward ref.
-                pkg.backward = null;
-                // visited init
-                groupVisited[groupId][pkg.name] = -1;
-            });
+            // _.forEach(groupPkgList, function(pkgName) {
+            //     var pkg = _find(pkgName);
+            //     // backward ref.
+            //     pkg.backward = null;
+            //     // visited init
+            //     groupVisited[groupId][pkg.name] = -1;
+            // });
             //logger.info('Group(' + groupId +  ') is uncheckable : ' + groupPkgList);
             return true;
         }
 
-        function _deleteConflictData(node) {
-            if (!_.isEmpty(node.conflicts)) {
-                _.forEach(node.conflicts, function(conflict) {
-                    if (!_.isEmpty(conflicts[conflict.name])) {
-                        var cList = conflicts[conflict.name];
-                        for(var i=0; i<cList.length; i++) {
-                            if (cList[i].name === node.name) {
-                                cList.splice(i, 1);
-                                break;
-                            }
-                        }
-                    }
-                });
-            }
-        }
-
         function _analyzeUncheck(parent, node) {
             if (!_.isEmpty(parent)) {
                 // TODO: performance
@@ -829,7 +1066,7 @@ define([
                     // updates pkgs for uncheck
                     if (!_.isEmpty(result)) {
                         _.forEach(result, function(value, key) {
-                            if (!_.has(uncheckPkgs, key)) {
+                            if (!(key in uncheckPkgs)) {
                                 uncheckPkgs[key] = value;
                             }
                         });
@@ -839,25 +1076,31 @@ define([
                 node.forward = null;
                 node.group = null;
             }
-            
+
             // delete conflict data from conflicts table
-            _deleteConflictData(node);
+            // _deleteConflictData(node);
 
             return uncheckPkgs;
         }
 
         // { 'group_id' : {pkgName: -1, ... }, ... }
-        var groupVisited = {};
-        var uncheckPkgs = _analyzeUncheck(null, uncheckNode);
+        // var groupVisited = {};
+        // var uncheckPkgs = _analyzeUncheck(null, uncheckNode);
 
         // delete groupId from groups
-        _.forEach(groupVisited, function(groupList, groupId) {
-            if (!_.isEmpty(groups[groupId])) {
-                delete groups[groupId];
+        // _.forEach(groupVisited, function(groupList, groupId) {
+        //     if (!_.isEmpty(groups[groupId])) {
+        //         delete groups[groupId];
+        //     }
+        // });
+        // return uncheckPkgs;
+        if (!_.isEmpty(uncheckNode.backward)) {
+            // check circular dependency
+            if (!uncheckNode.group || !_checkCircularDependency(uncheckNode)) {
+                return false;
             }
-        });
-
-        return uncheckPkgs;
+        }
+        return true;
     }
 
     /**
@@ -944,12 +1187,53 @@ define([
         }
     }
 
+    function reverseCheckAndAlert(message, pkgNode, viewNode, check) {
+        Util.showAlertDialog(message);
+        pkgNode.checked = check;
+        let toggleNodes = [];
+        if(check === true) {
+            $tree.treeview('checkNode', [[viewNode], { silent: true }]);
+        } else {
+            $tree.treeview('uncheckNode', [[viewNode], { silent: true }]);
+        }
+    }
+
+    function _checkDependencyPackages(dendencyPkgs) {
+        let checkNodes = [];
+        let uncheckNodes = [];
+        _.forEach(pkgInfo, function(node, pkgName){
+            if(pkgName in dendencyPkgs) {
+                node['checked'] = true;
+                // check node
+                _.forEach(node.view, function(vnode) {
+                    if (vnode.state.checked === false) {
+                        checkNodes.push(vnode);
+                    }
+                });
+            } else {
+                if (!_.isEmpty(node)){
+                    // uncheck node
+                    node['checked'] = false;
+                    _.forEach(node.view, function(vnode) {
+                        if (vnode.state.checked === true) {
+                            uncheckNodes.push(vnode);
+                        }
+                    });
+                } else {
+                    logger.info(pkgName);
+                }
+            }
+        });
+        $tree.treeview('checkNode', [checkNodes, { silent: true }]);
+        $tree.treeview('uncheckNode', [uncheckNodes, { silent: true }]);
+    }
+
     /**
      * Treeview: A node is checked.
      */
     function _onNodeChecked(event, node) {
-        var startTS = performance.now();
-        var localNode = _findFromNode(node);
+        let startTS = performance.now();
+        let localNode = _findFromNode(node);
 
         if (_.isEmpty(localNode)) {
             // MISC is virtual pacakge.
@@ -962,48 +1246,34 @@ define([
 
         logger.info('[Check] checked: ' + localNode.name);
         _nodeSelected(event, node);
-
         $tree.treeview('expandNode', [node, { silent: false }]);
 
         localNode.selfChecked = true;
-        var depResult = _analyzeInstallDependency(localNode)
+        // 1. pre-dependency analysis
+        let rpmcheck = _checkDependencyValidation(localNode);
+        if (rpmcheck.result === false) {
+            localNode.selfChecked = false;
+            reverseCheckAndAlert(rpmcheck.message, localNode, node, false);
+            return;
+        }
 
+        let depResult = _analyzeInstallDependency(localNode, rpmcheck.data);
         // Removing all references because the node can not be checked
         // Case: conflicts, rpm does not exist...
         if (depResult.result === false) {
-            Util.showAlertDialog(depResult.message);
-            localNode.checked = true;
-            $tree.treeview('uncheckNode', [node, { silent: false }]);
+            localNode.selfChecked = false;
+            reverseCheckAndAlert(depResult.message, localNode, node, false);
+            return;
         }
 
-        var analyzeTS = performance.now();
-
-        // TODO: debug code (should be deleted)
-        // var tempcode = [];
-        // _.forEach(depPkg, function(value, key) { tempcode.push(value.name); });
-        // logger.info(localNode.name + ' install-dependency(' + tempcode.length +'): ' + tempcode);
-
-        var toggleNode = [];
-        if (!_.isEmpty(depResult.data)) {
-            _.forEach(depResult.data, function(value, key) {
-                // update local data
-                value.checked = true;
-                // update treeview data
-                _.forEach(value.view, function(node) {
-                    if (node.state.checked === false) {
-                        toggleNode.push(node);
-                    }
-                });
-            });
-
-            $tree.treeview('checkNode', [toggleNode, { silent: true }]);
-            _updateSummary();
-
-            var endTS = performance.now();
-            logger.info('[Check] Total time: ' + (endTS - startTS) + 'ms');
-            logger.info('[Check] Analyze dep. time: ' + (analyzeTS - startTS) + 'ms');
-            logger.info('[Check] Update view time: ' + (endTS - analyzeTS) + 'ms');
-        }
+        let analyzeTS = performance.now();
+        selfCheckedMap[localNode.name] = localNode;
+        _checkDependencyPackages(depResult.data)
+        _updateSummary();
+        var endTS = performance.now();
+        logger.info('[Check] Total time: ' + (endTS - startTS) + 'ms');
+        logger.info('[Check] Analyze dep. time: ' + (analyzeTS - startTS) + 'ms');
+        logger.info('[Check] Update view time: ' + (endTS - analyzeTS) + 'ms');
     }
 
     /**
@@ -1023,35 +1293,26 @@ define([
         }
 
         logger.info('unchecked: ' + node.text);
-
-        var uncheckPkgs = _analyzeUncheckDependency(localNode);
-
-        var analyzeTS = performance.now();
-
-        // TODO: debug code (should be deleted)
-        // var tempcode = [];
-        // _.forEach(uncheckPkgs, function(value, key) { tempcode.push(value.name); });
-        // logger.info(localNode.name + ' uncheck-dependency(' + tempcode.length +'): ' + tempcode);
-
-        var toggleNode = [];
-        if (!_.isEmpty(uncheckPkgs)) {
-            _.forEach(uncheckPkgs, function(value, key) {
-                // update local data
-                value.checked = false;
-                // update treeview data
-                _.forEach(value.view, function(node) {
-                    if (node.state.checked === true) {
-                        toggleNode.push(node);
-                    }
-                });
-            });
-
-            $tree.treeview('uncheckNode', [toggleNode, { silent: true }]);
+        if (_analyzeUncheckDependency(localNode)) {
             localNode.selfChecked = false;
+            delete selfCheckedMap[localNode.name];
+
+            // 1. pre-dependency analysis
+            let rpmcheck = _checkDependencyValidation(null);
+            if (rpmcheck.result === false) {
+                reverseCheckAndAlert(depResult.message, localNode, node, false);
+                return;
+            }
+            let depResult = _analyzeInstallDependency(null, rpmcheck.data);
+            if (depResult.result === false) {
+                reverseCheckAndAlert(depResult.message, localNode, node, false);
+                return;
+            }
+            var analyzeTS = performance.now();
+            _checkDependencyPackages(depResult.data);
             _updateSummary();
         } else {
             Util.showAlertDialog('Could not uncheck the \'' +localNode.name + '\'' + '<br> because the \'' +  localNode.backward + '\' packages depends on it');
-
             // selected node change to check state
             $tree.treeview('checkNode', [[node], { silent: true }]);
         }
@@ -1070,7 +1331,6 @@ define([
         pkgInfo = rawData.data.packages
         provides = rawData.data.provides;
         files = rawData.data.files;
-        conflicts = rawData.data.conflicts;
         groups = rawData.data.groups;
 
         _.forEach(groups, function(value, key) {
@@ -1091,7 +1351,7 @@ define([
                          }
                      }
                 });
-                _setDefaultPackage(rawData.defaultpackages);
+                _setDefaultPackage(rawData.installpackages);
                 _updateSummary();
 
                 // init filter and restore checkbox state
@@ -1260,17 +1520,13 @@ define([
             _.forEach(pkgInfo, function(value, key) {
                 value.checked = false;
                 value.selfChecked = false;
-                value.forward = null;
-                value.backward = null;
-                value.group = null;
-
                 // INFO: bug for state.checked = false in treeview objects
                 _.forEach(value.view, function(node) {
                     node.state.checked = false;
                 });
             });
-            groups = {};
-            conflicts = {};
+            selfCheckedMap = {};
+            requiredInstPkgs = null;
             _updateSummary();
         }
         $tree.contextmenu({