# pylint: disable=invalid-name,missing-docstring import copy from collections import OrderedDict from Logger import Logger from constants import constants from SpecData import SPECS from StringUtils import StringUtils def removeDuplicateEntries(myList): myListCopy = list(OrderedDict.fromkeys(myList)) return myListCopy class PackageBuildDataGenerator(object): cycleCount = 0 def __init__(self, logName=None, logPath=None): if logName is None: logName = "PackageBuildDataGenerator" if logPath is None: logPath = constants.logPath self.logger = Logger.getLogger(logName, logPath, constants.logLevel) self.__mapCyclesToPackageList = {} self.__mapPackageToCycle = {} self.__buildDependencyGraph = {} self.__runTimeDependencyGraph = {} self.__sortedPackageList = [] self.__sortedBuildDependencyGraph = {} def getPackageBuildData(self, listPackages): basePackages = [] for pkg in listPackages: basePackages.append(SPECS.getData().getBasePkg(pkg)) self._readDependencyGraphAndCyclesForGivenPackages(basePackages) self._getSortedBuildOrderList() return self.__mapCyclesToPackageList, self.__mapPackageToCycle, self.__sortedPackageList #todo def _findAllPackagesToBuild(self): return list(self.__buildDependencyGraph.keys()) def _createSortListForPkg(self, pkg): runTimeDepPkgList = list(set(self.__runTimeDependencyGraph[pkg])) runTimeDepPkgList.append(pkg) sortListForPkg = [] for p in runTimeDepPkgList: basePkg = SPECS.getData().getBasePkg(p) for bPkg in self.__sortedBuildDependencyGraph[basePkg]: if bPkg not in sortListForPkg: sortListForPkg.append(bPkg) return sortListForPkg def _getCircularDependentPackages(self, pkg): circularDependentPackages = [] if pkg in self.__mapPackageToCycle: circularDependentPackages.extend( self.__mapCyclesToPackageList[self.__mapPackageToCycle[pkg]]) circularDependentPackages.remove(pkg) return circularDependentPackages def _getSortedBuildOrderList(self): alreadyProcessedPackages = set() sortedList = [] completeListPackagesToBuild = self._findAllPackagesToBuild() packageIndexInSortedList = 0 prevSortListLen = 0 while completeListPackagesToBuild: # find next package to process pkg = None index = -1 lenList = len(sortedList) for i in range(lenList): if sortedList[i] in alreadyProcessedPackages: continue pkg = sortedList[i] packageIndexInSortedList = i break if pkg is None: pkg = completeListPackagesToBuild.pop() packageIndexInSortedList = len(sortedList) #creating sort list for package sortListForPkg = self._createSortListForPkg(pkg) #remove any cyclic packages in sortListForPkg if they already #exists in sortedList circularDependentPackages = self._getCircularDependentPackages(pkg) for p in circularDependentPackages: if p in sortedList and p in sortListForPkg: sortListForPkg.remove(p) # insert sort list of package in global sorted list index = packageIndexInSortedList subList = [] if packageIndexInSortedList > 0: subList = sortedList[:packageIndexInSortedList] for p in sortListForPkg: if p not in subList: sortedList.insert(index, p) index = index + 1 alreadyProcessedPackages.add(p) # Remove duplicate entries in sorted list in intervals if (len(sortedList) - prevSortListLen) > 100: self.logger.debug("Removing duplicates in sortedList") sortedList = removeDuplicateEntries(sortedList) else: prevSortListLen = len(sortedList) self.logger.debug("Removing duplicates in sorted list") sortedList = removeDuplicateEntries(sortedList) self.logger.debug("Sorted list: ") self.logger.debug(sortedList) self.__sortedPackageList = sortedList def _constructBuildAndRunTimeDependencyGraph(self, basePackage): addBuildTimeGraph = True addRunTimeGraph = True if basePackage in self.__buildDependencyGraph: addBuildTimeGraph = False if basePackage in self.__runTimeDependencyGraph: addRunTimeGraph = False nextPackagesToConstructGraph = set() if addBuildTimeGraph: dependentRpmPackages = SPECS.getData().getBuildRequiresForPkg(basePackage) dependentPackages = set() for dependentPkg in dependentRpmPackages: dependentPackages.add(SPECS.getData().getBasePkg(dependentPkg)) self.__buildDependencyGraph[basePackage] = dependentPackages nextPackagesToConstructGraph.update(dependentPackages) if addRunTimeGraph: dependentPackages = set() for rpmPkg in SPECS.getData().getPackagesForPkg(basePackage): dependentRpmPackages = SPECS.getData().getRequiresAllForPkg(rpmPkg) self.__runTimeDependencyGraph[rpmPkg] = copy.copy(set(dependentRpmPackages)) for pkg in dependentRpmPackages: dependentPackages.add(SPECS.getData().getBasePkg(pkg)) nextPackagesToConstructGraph.update(dependentPackages) for pkg in nextPackagesToConstructGraph: self._constructBuildAndRunTimeDependencyGraph(pkg) def _readDependencyGraphAndCyclesForGivenPackages(self, basePackages): self.logger.debug("Reading dependency graph to check for cycles") for pkg in basePackages: self._constructBuildAndRunTimeDependencyGraph(pkg) for pkg in self._findAllPackagesToBuild(): sortedPackageList, circularDependentPackages = self._topologicalSortPackages( self.__buildDependencyGraph, pkg) if circularDependentPackages: self.logger.error("Found circular dependency") self.logger.error(circularDependentPackages) raise Exception("Build Time Circular Dependency") self.__sortedBuildDependencyGraph[pkg] = sortedPackageList sortedPackageList, circularDependentPackages = self._topologicalSortPackages( self.__runTimeDependencyGraph) if circularDependentPackages: self._findCircularDependencies(circularDependentPackages) @staticmethod def _buildDependentPackages(dependencyGraph, package): dependentPackages = {} if package is None: dependentPackages = copy.deepcopy(dependencyGraph) else: depPkgs = set() depPkgs.add(package) while depPkgs: pkg = depPkgs.pop() if pkg in dependentPackages: continue dependentPackages[pkg] = copy.copy(dependencyGraph[pkg]) for depPkg in dependencyGraph[pkg]: depPkgs.add(depPkg) return dependentPackages @staticmethod def _buildDependentOfPackages(dependentPackages): dependentOfPackage = dict() for pkg in dependentPackages: if dependentPackages[pkg]: for depPkg in dependentPackages[pkg]: if depPkg not in dependentOfPackage: dependentOfPackage[depPkg] = {pkg} else: dependentOfPackage[depPkg].add(pkg) return dependentOfPackage @staticmethod def _topologicalSortPackages(dependencyGraph, package=None): sortedPackageList = [] noDepPackages = set() dependentPackages = PackageBuildDataGenerator._buildDependentPackages( dependencyGraph, package) dependentOfPackage = PackageBuildDataGenerator._buildDependentOfPackages( dependentPackages) #Find packages with no dependencies and generate dependentof_package edge list for pkg in dependentPackages: if not dependentPackages[pkg]: noDepPackages.add(pkg) while noDepPackages: pkg = noDepPackages.pop() sortedPackageList.append(pkg) if pkg in dependentOfPackage: for childPkg in list(dependentOfPackage[pkg]): dependentOfPackage[pkg].remove(childPkg) dependentPackages[childPkg].remove(pkg) if not dependentPackages[childPkg]: noDepPackages.add(childPkg) # creating circular dependency graph for given dependency graph circularDependencyGraph = {} for pkg in dependentPackages.keys(): if dependentPackages[pkg]: circularDependencyGraph[pkg] = dependentPackages[pkg] #return (non-circular dependent package in sorted order and circular dependent #package list in a dependencyGraph) return sortedPackageList, circularDependencyGraph def _constructDependencyMap(self, cyclicDependencyGraph): self.logger.debug("Constructing dependency map from circular dependency graph.....") constructDependencyMap = {} for node in cyclicDependencyGraph.keys(): tmpDepNodeList = set() tmpDepNodeList.add(node) depNodeList = [] while tmpDepNodeList: currentNode = tmpDepNodeList.pop() addDepNodeList = cyclicDependencyGraph[currentNode] depNodeList.append(currentNode) for depNode in addDepNodeList: if depNode in depNodeList: continue else: if depNode not in tmpDepNodeList: tmpDepNodeList.add(depNode) depNodeList.remove(node) constructDependencyMap[node] = depNodeList self.logger.debug("Dependency Map:") self.logger.debug(constructDependencyMap) return constructDependencyMap def _findCircularDependencies(self, cyclicDependencyGraph): self.logger.debug("Looking for circular dependencies") if not cyclicDependencyGraph: return #step1: construct dependency map from dependency graph constructDependencyMap = self._constructDependencyMap(cyclicDependencyGraph) #step2: find cycles in dependency map self.logger.debug("Finding and adding cycles using constructed dependency map......") cycleCount = 0 for node in cyclicDependencyGraph.keys(): listDepPkg = constructDependencyMap[node] cycPkgs = [] if node not in self.__mapPackageToCycle: for depPkg in listDepPkg: x = constructDependencyMap[depPkg] if node in x: cycPkgs.append(depPkg) if cycPkgs: cycPkgs.append(node) cycleName = "cycle" + str(PackageBuildDataGenerator.cycleCount) PackageBuildDataGenerator.cycleCount += 1 for x in cycPkgs: self.__mapPackageToCycle[x] = cycleName self.__mapCyclesToPackageList[cycleName] = cycPkgs self.logger.debug("New circular dependency found:") self.logger.debug(cycleName + " " + ",".join(cycPkgs)) cycleCount += 1 if cycleCount > 0: self.logger.debug("Found " + str(cycleCount) + " cycles.") self.logger.debug("Successfully added all detected circular dependencies to list.") else: self.logger.debug("No circular dependencies found.")