Package conary :: Package conaryclient :: Module resolve
[hide private]
[frames] | no frames]

Source Code for Module conary.conaryclient.resolve

  1  # 
  2  # Copyright (c) 2004-2008 rPath, Inc. 
  3  # 
  4  # This program is distributed under the terms of the Common Public License, 
  5  # version 1.0. A copy of this license should have been distributed with this 
  6  # source file in a file called LICENSE. If it is not present, the license 
  7  # is always available at http://www.rpath.com/permanent/licenses/CPL-1.0. 
  8  # 
  9  # This program is distributed in the hope that it will be useful, but 
 10  # without any warranty; without even the implied warranty of merchantability 
 11  # or fitness for a particular purpose. See the Common Public License for 
 12  # full details. 
 13   
 14  import itertools 
 15   
 16  from conary import errors 
 17  from conary.lib import log 
 18  from conary.repository import searchsource 
 19  from conary.repository.resolvemethod import DepResolutionByTroveList, \ 
 20      ResolutionStack, BasicResolutionMethod, DepResolutionByLabelPath, \ 
 21      DepResolutionMethod 
 22   
23 -class DependencySolver(object):
24
25 - def __init__(self, client, cfg, repos, db):
26 self.client = client 27 self.cfg = cfg 28 self.repos = repos 29 self.db = db
30
31 - def _findCriticalJobInfo(self, jobSet, updateSettings):
32 if updateSettings is None: 33 return [], [], False 34 criticalJobs = updateSettings.findCriticalJobs(jobSet) 35 finalJobs = updateSettings.findFinalJobs(jobSet) 36 return criticalJobs, finalJobs, updateSettings.isCriticalOnlyUpdate()
37
38 - def resolveDependencies(self, uJob, jobSet, split = False, 39 resolveDeps = True, useRepos = True, 40 resolveSource = None, keepRequired = True, 41 criticalUpdateInfo = None):
42 """ 43 Determine and possibly resolve dependency problems. 44 @param uJob: update job we are resolving dependencies for 45 @type uJob: local.database.UpdateJob 46 @param jobSet: jobs that are to be applied 47 @type jobSet: list of job tuples 48 @param split: if True, find an ordering for the jobs in 49 jobSet plus any resolved jobs. 50 @type split: bool 51 @param resolveDeps: If True, try to resolve any dependency problems 52 by modifying the given job. 53 @type resolveDeps: bool 54 @param useRepos: If True, search for dependency solutions in the 55 repository after searching the update job search source. 56 @param keepRequired: If True, the resolver will attempt to remove 57 erase jobs from the job set to resolve dependency problems 58 created by the erasure. If False, those problems will be reported 59 instead. 60 @type keepRequired: bool 61 """ 62 searchSource = uJob.getSearchSource() 63 if not isinstance(searchSource, searchsource.AbstractSearchSource): 64 searchSource = searchsource.SearchSource(searchSource, 65 self.cfg.flavor, self.db) 66 if useRepos: 67 defaultSearchSource = self.client.getSearchSource() 68 searchSource = searchsource.stack(searchSource, 69 defaultSearchSource) 70 troveSource = searchSource.getTroveSource() 71 72 ineligible = set() 73 74 check = self.db.getDepStateClass(uJob.getTroveSource()) 75 76 (depList, cannotResolve, changeSetList, keepList, ineligible, 77 criticalUpdates) = self.checkDeps(uJob, jobSet, troveSource, 78 findOrdering = split, 79 resolveDeps = resolveDeps, 80 ineligible=ineligible, 81 keepRequired = keepRequired, 82 criticalUpdateInfo = criticalUpdateInfo, 83 check = check) 84 85 if not resolveDeps: 86 # we're not supposed to resolve deps here; just skip the 87 # rest of this 88 depList = [] 89 cannotResolve = [] 90 91 if resolveSource is None: 92 resolveSource = searchSource.getResolveMethod() 93 resolveSource.searchLeavesFirst() 94 else: 95 resolveSource.setTroveSource(troveSource) 96 97 suggMap = {} 98 99 if not hasattr(resolveSource, 'filterDependencies'): 100 # add the identity fn for this new api 101 resolveSource.filterDependencies = lambda x: x 102 depList = resolveSource.filterDependencies(depList) 103 while resolveSource.prepareForResolution(depList): 104 105 sugg = resolveSource.resolveDependencies() 106 newTroves = resolveSource.filterSuggestions(depList, sugg, suggMap) 107 108 if not newTroves: 109 continue 110 111 changedJob = self.addUpdates(newTroves, uJob, jobSet, ineligible, 112 keepList, troveSource, resolveSource) 113 if not changedJob: 114 continue 115 116 (depList, cannotResolve, changeSetList, newKeepList, 117 ineligible, criticalUpdates) = self.checkDeps(uJob, jobSet, 118 uJob.getTroveSource(), 119 findOrdering = True, 120 resolveDeps = True, 121 ineligible = ineligible, 122 keepRequired = keepRequired, 123 criticalUpdateInfo = criticalUpdateInfo, 124 check = check) 125 keepList.extend(newKeepList) 126 depList = resolveSource.filterDependencies(depList) 127 128 check.done() 129 130 if criticalUpdateInfo is None: 131 # backwards compatibility with conary v. 1.0.30/1.1.3 and earlier 132 return (depList, suggMap, cannotResolve, changeSetList, keepList) 133 return (depList, suggMap, cannotResolve, changeSetList, keepList, 134 criticalUpdates)
135
136 - def addUpdates(self, troves, uJob, jobSet, ineligible, keepList, 137 troveSource, resolveSource):
138 """ 139 Add the given dep resolution solutions to the current jobSet. 140 """ 141 # We found good suggestions, merge in those troves. Items 142 # which are being removed by the current job cannot be 143 # removed again. 144 beingRemoved = set((x[0], x[1][0], x[1][1]) for x in 145 jobSet if x[1][0] is not None ) 146 beingInstalled = set((x[0], x[2][0], x[2][1]) for x in 147 jobSet if x[2][0] is not None ) 148 149 150 # add in foo if we are adding foo:lib. That way 'conary 151 # erase foo' will work as expected. 152 if self.cfg.autoResolvePackages: 153 packageJobs = self.addPackagesForComponents(troves, 154 troveSource, 155 beingInstalled) 156 troves.update(packageJobs) 157 158 newJob = self.client._updateChangeSet(troves, uJob, 159 keepExisting = False, 160 recurse = False, 161 ineligible = beingRemoved, 162 checkPrimaryPins = True) 163 assert(not (newJob & jobSet)) 164 newJob = resolveSource.filterResolutionsPostUpdate(self.db, newJob, 165 troveSource) 166 if not newJob: 167 # we had potential solutions, but they would have 168 # required implicitly switching the branch of a trove 169 # on a user, and we don't do that. 170 return False 171 172 jobSet.update(newJob) 173 return True
174
175 - def checkDeps(self, uJob, jobSet, trvSrc, findOrdering, 176 resolveDeps, ineligible, keepRequired = True, 177 criticalUpdateInfo = None, check = None):
178 """ 179 Given a jobSet, use its dependencies to determine an 180 ordering, resolve problems with jobs that have difficult 181 dependency problems immediately, 182 and determine any missing dependencies. 183 """ 184 assert(check) 185 186 keepList = [] 187 188 while True: 189 linkedJobs = self.client._findOverlappingJobs(jobSet, 190 uJob.getTroveSource()) 191 criticalJobs, finalJobs, criticalOnly = self._findCriticalJobInfo( 192 jobSet, 193 criticalUpdateInfo) 194 (depList, cannotResolve, changeSetList, criticalUpdates) = \ 195 check.depCheck(jobSet, 196 findOrdering = findOrdering, 197 linkedJobs = linkedJobs, 198 criticalJobs = criticalJobs, 199 finalJobs = finalJobs, 200 criticalOnly = criticalOnly) 201 202 if not resolveDeps or not cannotResolve: 203 break 204 # We have troves that are in the state cannotResolve: 205 # This means that they are troves that are needed by 206 # something else that we are trying to erase. 207 208 # We attempt to solve them here in this inner loop because 209 # they are more difficult to resolve, and resolving them may 210 # affect resolving normal missing dependencies. 211 212 changeMade = False 213 214 # first: attempt to update packages that dependended on the missing 215 # package. 216 log.debug('Update breaks dependencies!') 217 for reqInfo, depSet, provInfo in sorted(cannotResolve, 218 key=lambda x:x[0][0]): 219 depSet = '\n '.join(str(depSet).split('\n')) 220 msg = ( 221 'Broken dependency: (dep needed by the system but being removed):\n' 222 ' %s=%s\n' 223 ' Requires:\n' 224 ' %s\n' 225 ' Provided by removed or updated packages: %s') 226 args = (reqInfo[0], reqInfo[1].trailingRevision(), depSet, 227 ', '.join(x[0] for x in provInfo)) 228 log.debug(msg, *args) 229 if self.cfg.resolveLevel > 1: 230 cannotResolve, newJobSet = self.resolveEraseByUpdating( 231 trvSrc, 232 cannotResolve, 233 uJob, jobSet, 234 ineligible, 235 check) 236 if newJobSet: 237 jobSet |= newJobSet 238 changeMade = True 239 240 if not changeMade and cannotResolve and keepRequired: 241 # second: attempt to keep packages that were being erased 242 cannotResolve, newKeepList = self.resolveEraseByKeeping( 243 trvSrc, 244 cannotResolve, 245 uJob, jobSet) 246 if newKeepList: 247 changeMade = True 248 keepList += newKeepList 249 250 if not changeMade: 251 break 252 253 return (depList, cannotResolve, changeSetList, keepList, ineligible, 254 criticalUpdates)
255
256 - def resolveEraseByUpdating(self, trvSrc, cannotResolve, uJob, jobSet, 257 ineligible, check):
258 """ 259 Attempt to resolve broken erase dependencies by updating the 260 package that has the dependency on the trove that is being 261 erased. 262 @param ineligible: Ineligible troves are troves that were 263 involved in resolveEraseByUpdating before. 264 """ 265 266 oldIdx = {} 267 newIdx = {} 268 for job in jobSet: 269 if job[1][0] is not None: 270 oldIdx[(job[0], job[1][0], job[1][1])] = job 271 if job[2][0] is not None: 272 newIdx[(job[0], job[2][0], job[2][1])] = job 273 274 potentialUpdateList = [] 275 276 for resolveInfo in cannotResolve: 277 (reqInfo, depSet, provInfoList) = resolveInfo 278 # reqInfo = the trove the requires the dependency 279 # provInfo = the troves that provide the dependency 280 281 if reqInfo in newIdx: 282 # The thing with the requirement is something we asked 283 # to be installed - don't try to update it again! 284 continue 285 286 found = False 287 for provInfo in provInfoList: 288 if provInfo in ineligible: 289 # The trove that we erased due to an earlier 290 # resolveEraseByUpdating was required by other troves! 291 # We don't allow this process to recurse to avoid 292 # accidentally updating your entire system due to 293 # a glibc update request, e.g. 294 found = True 295 break 296 if found: 297 continue 298 299 for provInfo in provInfoList: 300 if provInfo not in oldIdx: 301 continue 302 303 job = oldIdx[provInfo] 304 305 if not job[2][0]: 306 # this was an erase, not an update, we don't attempt 307 # use this method on erases. 308 continue 309 310 # we're trying to update this package. 311 # don't allow recursion to take place, where things that 312 # require the reqInfo are updated. 313 ineligible.add(reqInfo) 314 potentialUpdateList.append((reqInfo, depSet, provInfoList)) 315 break 316 317 if not potentialUpdateList: 318 return cannotResolve, set() 319 320 log.debug('Attempting to update the following packages in order to ' 321 'remove their dependency on something being erased:\n %s', ('\n '.join(sorted(x[0][0].split(':')[0] for x in potentialUpdateList)))) 322 323 # attempt to update the _package_ that has the requirement that 324 # is now erased. 325 updateJobs = [ (x[0][0].split(':')[0], 326 (None,