# Copyright (C) 2005 Enrico Scholz # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. from Filter import * import sys import rpm import copy #### REMOVE-ME sys.path = ['/usr/share/rpmlint',] + sys.path import AbstractCheck class _EVR: def __init__(this, label): this.evr = this.__canonifyLabel(label) def __canonifyLabel(this, label): if label==None: tmp = ["0", "0-0"] else: tmp = label.split(':') if len(tmp)==1: epoch="0" elif len(tmp)==2: epoch = tmp[0] del tmp[0] else: assert(False) tmp = tmp[0].split('-') if len(tmp)==1: release = "0" elif len(tmp)==2: release = tmp[1] else: assert(False) version = tmp[0] return (epoch, version, release) def __str__(this): return "%s:%s-%s" % this.evr class _APoint: def __init__(this, v=None, e=0): assert(isinstance(e,int)) this.__v = v # value this.__e = e # True iff |epsilon|>0 this.__pos = 0 def __cmp__(this, rhs): if rhs==None: return +1 assert(isinstance(rhs, _APoint)) if this.__v==None: if rhs.__v==None: rc = -(this.__e - rhs.__e) elif this.__e!=0: rc = -this.__e else: assert(False) elif rhs.__v == None: if rhs.__e!=0: rc = rhs.__e else: assert(False) else: rc = this._compareInternal(this.__v, rhs.__v) if rc==0: rc = this.__e - rhs.__e #print "__cmp__(%s, %s) -> %u" % (this, rhs, rc) return rc def isNearly(this, rhs): if rhs==None: return False assert(isinstance(rhs, _APoint)) return ((this.__v==None and rhs.__v==None and this.__e*rhs.__e > 0) or (this.__v!=None and rhs.__v!=None and this._compareInternal(this.__v, rhs.__v)==0)) def clone(this): return copy.deepcopy(this) def getValue(this): return this.__v; def isInfinite(this, pos): return (pos==this.__pos and this.__v==None) def setPosition(this, pos): this.__pos = pos return this def unsetEpsilon(this): assert(this.__e!=0) assert(this.__pos!=0) this.__e = 0 def setEpsilon(this): assert(this.__e==0) assert(this.__pos!=0) if this.__pos > 0: this.__e = -1 else: this.__e = +1 def revert(this): assert(this.__pos!=0) this.__pos = -this.__pos if this.__e!=0: this.__e = 0 else: this.setEpsilon() def update(this, rhs): assert(rhs==None or isinstance(rhs, _APoint)) this.__v = rhs.__v this.__e = rhs.__e return this def _updateInternal(this, v, e): assert(isinstance(e,int)) this.__v = v this.__e = e def _verify(this): assert(this.__pos!=0 or this.__e==0) assert(this.__pos>0 or this.__e>=0) assert(this.__pos<0 or this.__e<=0) def __str__(this): this._verify() if this.__pos<0: if this.__v==None: res = "(-INF" elif this.__e: res = "(%s" % this.__v else: res = "[%s" % this.__v elif this.__pos>0: if this.__v==None: res = "+INF)" elif this.__e: res = "%s)" % this.__v else: res = "%s]" % this.__v else: if this.__v==None: if this.__e<0: res = "+INF" else: res = "-INF" else: if this.__e<0: res = "%s-" % this.__v if this.__e>0: res = "%s+" % this.__v else: res = "%s" % this.__v return res class _EVRPoint(_APoint): def __init__(this, v=None, e=0): _APoint.__init__(this, v, e) def __cmp__(this, rhs): if isinstance(rhs, _EVR): rhs = _EVRPoint(rhs, 0) return _APoint.__cmp__(this, rhs) def _compareInternal(this, lhs, rhs): assert(isinstance(lhs, _EVR)) assert(isinstance(rhs, _EVR)) return rpm.labelCompare(lhs.evr, rhs.evr) def updateByEVR(this, v, e): assert(v==None or isinstance(v, _EVR)) _APoint._updateInternal(this, v, e) class _ARange: def __init__(this, klass): this._lo = klass(None, +1).setPosition(-1) this._hi = klass(None, -1).setPosition(+1) this.__klass = klass def _canonifyRel(this, rel): if rel==None: return None elif rel=='<=': return (-1, 0) elif rel=='<': return (-1, -1) elif rel=='=': return ( 0, 0) elif rel=='>': return (+1, +1) elif rel=='>=': return (+1, 0) assert(False) def clone(this): return copy.deepcopy(this) def __cloneEmptyRange(this): res = this.clone() res._lo = res._hi = this.klass(None, +1).setPosition(-1) return res def contains(this, v): return this._lo<=v and this._hi>=v def isEmpty(this): return this._lo == this._hi def isInfinite(this): return this._lo.isInfinite(-1) and this._hi.isInfinite(+1) def __str__(this): return "%s..%s" % (this._lo, this._hi) def _verify(this): assert(this._lo <= this._hi) def __subtractPoint(this, rhs): assert(isinstance(rhs, _APoint)) res = this.clone() if this._lo > rhs or this._hi < rhs: return (res,) if this._lo == rhs: res._lo.setEpsilon() return (res,) if this._hi == rhs: res._hi.setEpsilon() return (res,) res1 = this.clone() res._hi = rhs.clone() res._hi.setPosition(+1) res._hi.setEpsilon() res1._lo = rhs.clone() res1._lo.setPosition(-1) res1._lo.setEpsilon() return (res, res1) def __subtractRange(this, rhs): assert(isinstance(rhs, _ARange)) res = this.clone() if rhs._hi < this._lo or this._hi < rhs._lo: return (res,) if rhs._lo <= this._lo and this._hi <= rhs._hi: return () if rhs._lo <= this._lo: res._lo = rhs._hi.clone() res._lo.revert() return (res,) if this._hi <= rhs._hi: res._hi = rhs._lo.clone() res._hi.revert() return (res,) res._hi = rhs._lo.clone() res._hi.revert() res1 = this.clone() res1._lo = rhs._hi.clone() res1._lo.revert() return (res, res1) def __addPoint(this, rhs): assert(isinstance(rhs, _APoint)) res = this.clone() if this._lo <= rhs and this._hi >= rhs: return (res,) if this._lo.isNearly(rhs): res._lo.unsetEpsilon() return (res,) if this._hi.isNearly(rhs): res._hi.unsetEpsilon() return (res,) res1 = this.clone() res1._lo = rhs.clone() res1._hi = rhs.clone() return (res, res1) def subtract(this, rhs): rhs._verify() this._verify() if this._lo == this._hi: return () if isinstance(rhs, _APoint): return this.__subtractPoint(rhs) if isinstance(rhs, _ARange): return this.__subtractRange(rhs) assert(False) def add(this, rhs): rhs._verify() this._verify() if isinstance(rhs, _APoint): return this.__addPoint(rhs) if isinstance(rhs, _ARange): return _ARange.merge((this, rhs)) assert(False) def __mergeSingle(this, rhs): assert(isinstance(rhs, _ARange)) assert(this < rhs) assert(this._lo <= rhs._lo) this._verify() rhs._verify() if this._lo == rhs._lo: return (rhs.clone(),) if rhs._hi <= this._hi: return (this.clone(),) if rhs._lo <= this._hi: res = this.clone() res._hi = rhs._hi.clone() return (res,) return (this.clone(), rhs.clone()) def intersect(this, rhs): if rhs==None: return this.__cloneEmptyRange() assert(isinstance(rhs, _ARange)) this._verify() rhs._verify() if this > rhs: return rhs.intersect(this) assert(this._lo <= rhs._lo) if rhs._hi <= this._hi: return rhs.clone() if this._hi < rhs._lo: return this.__cloneEmptyRange() res = this.clone() res._lo = rhs._lo.clone() return res @staticmethod def merge(range_set): range_set = range_set[:] range_set.sort() res = [] cur_range = range_set[0] for i in xrange(1, len(range_set)): assert(cur_range <= range_set[i]) if cur_range == range_set[i]: continue tmp = cur_range.__mergeSingle(range_set[i]) assert(len(tmp)>=1) if len(tmp)==1: cur_range = tmp[0] else: res.append(tmp[0]) cur_range = tmp[1] res.append(cur_range) return res def __cmp__(this, rhs): if rhs==None: return +1 assert(isinstance(rhs, _ARange)) rc = cmp(this._lo, rhs._lo) if rc == 0: rc = cmp(this._hi, rhs._hi) return rc class _EVRRange(_ARange): def __init__(this): _ARange.__init__(this, _EVRPoint) def _canonifyRel(this, rel): if not isinstance(rel, int): return _ARange._canonifyRel(this, rel) if rel & (rpm.RPMSENSE_LESS|rpm.RPMSENSE_EQUAL): return (-1, 0) if rel & (rpm.RPMSENSE_LESS): return (-1,-1) if rel & (rpm.RPMSENSE_GREATER|rpm.RPMSENSE_EQUAL): return (+1, 0) if rel & (rpm.RPMSENSE_GREATER): return (+1,-1) if rel & (rpm.RPMSENSE_GREATER): return (+1,-1) if rel & (rpm.RPMSENSE_EQUAL): return ( 0, 0) assert(False) def subtract(this, rhs): if isinstance(rhs, _EVR): rhs = _EVRPoint(rhs) return _ARange.subtract(this, rhs) class _ConflictRange(_EVRRange): def __init__(this, rel, evr): _EVRRange.__init__(this) rel = this._canonifyRel(rel) if rel!=None and not isinstance(evr, _EVR): evr = _EVR(evr) if (rel==None): pass else: if rel[0]>=0: this._lo.updateByEVR(evr, rel[1]) if rel[0]<=0: this._hi.updateByEVR(evr, rel[1]) class _RequireRange(_EVRRange): def __init__(this): _EVRRange.__init__(this) def __updatePoint(this, p, rel): if rel[0]>0 and this._lo < p: this._lo.update(p) elif rel[0]<0 and this._hi > p: this._hi.update(p) elif rel[0]==0 and this._lo <= p and this._hi >= p: this._lo.update(p) this._hi.update(p) elif rel[0]==0: print this, p, rel, this._lo<=p, this._hi>=p assert(False) def update(this, rel, evr): rel = this._canonifyRel(rel) if rel!=None and not isinstance(evr, _EVR): evr = _EVR(evr) if rel!=None: this.__updatePoint(_EVRPoint(evr, rel[1]), rel) return this class _VersionedDependency: def __init__(this, name): this.__name = name this.__reqs = _RequireRange() this.__conflicts = [] this.__files = [] @staticmethod def createRequiresSet(deps): deps = deps[:] j = 0 res = [] while j < len(deps): if j+2 < len(deps) and deps[j+1] in ('<', '<=', '=', '>=', '>'): name = deps[j] rel = deps[j+1] evr = deps[j+2] j = j+3 else: name = deps[j] rel = None evr = None j = j+1 tmp = _VersionedDependency(name); tmp.addRequires(rel, evr) res.append(tmp) return res def add(this, rhs): assert(isinstance(rhs, _VersionedDependency)) this.__reqs = this.__reqs.intersect(rhs.__reqs) this.__conflicts.append(rhs.__conflicts) def addFile(this, file): this.__files.append(file) def getFiles(this): return this.__files def addRequires(this, rel=None, evr=None): this.__reqs.update(rel, evr) def addConflict(this, rel=None, ver=None): this.__conflicts.append(_ConflictRange(rel, evr)) def getRequires(this): return this.__reqs def getName(this): return this.__name def finish(this): this.__conflicts = _ARange.merge(this.__conflicts) def __cmp__(this, rhs): if rhs==None: return +1 assert(isinstance(rhs, _VersionedDependency)) rc = cmp(this.__name, rhs.__name) #if rc == 0: # rc = cmp(this.__reqs, rhs.__reqs) return rc def __str__(this): return "%s in %s" % (this.__name, this.__reqs) class DocFilesCheck(AbstractCheck.AbstractCheck): def __init__(self): AbstractCheck.AbstractCheck.__init__(self, 'DocFilesCheck') def __checkRequirements(this, pkg): file_reqs = pkg.header[rpm.RPMTAG_FILEREQUIRE] files = pkg.header[rpm.RPMTAG_FILENAMES] doc_files = pkg.docFiles() assert(len(file_reqs) == len(files)) reqs = {} seen_reqs = [] for i in xrange(0,len(files)): tmp = file_reqs[i].split() tmp = _VersionedDependency.createRequiresSet(tmp) reqs[files[i]] = tmp for dep in tmp: seen_reqs.extend(tmp) core_reqs = {} # dependencies of non-doc files doc_reqs = {} # dependencies of doc files # Register explicit 'Requires:' as core-requirements. Explicit # 'Requires:' are these deps which were *NOT* found by # 'find-requires' for i in pkg.header.dsFromHeader(): # deps of %doc files are generated by 'find-requires' so # we are interested in those entries only if (i.Flags() & (rpm.RPMSENSE_FIND_REQUIRES|rpm.RPMSENSE_FIND_PROVIDES))!=0: continue tmp = _VersionedDependency(i.N()) tmp.addRequires(i.Flags(), i.EVR()) # register explicit 'Requires:'; these are the RPMTAG_REQUIRES # which are not created by requirements of files if tmp not in seen_reqs: name = tmp.getName() if not core_reqs.has_key(name): core_reqs[name] = tmp else: core_reqs[name].add(tmp) # register things which are provided by the package for i in files: tmp = _VersionedDependency(i) if not core_reqs.has_key(i): core_reqs[i] = tmp else: core_reqs[i].add(tmp) for i in files: if not reqs[i]: continue # skip empty dependencies elif i in doc_files: target = doc_reqs else: target = core_reqs for r in reqs[i]: name = r.getName() if not target.has_key(name): target[name] = r else: target[name].add(tmp) target[name].addFile(i) # go through the calculated requirements of the %doc files for (dep,req_files) in doc_reqs.items(): found = False if dep not in core_reqs: diff = req_files.getRequires() found = True else: diff = req_files.getRequires().subtract(core_reqs[dep].getRequires()) if len(diff)!=0 and (len(diff)!=1 or not diff[1].isEmpty()): found = True if diff!=None and not diff.isInfinite(): diff = " in %s" % diff else: diff = "" if found: name = req_files.getName() for f in req_files.getFiles(): printError(pkg, "%%doc file '%s' creates additional dependency '%s%s'" % (f, name, diff)) def check(self, pkg): if pkg.isSource(): return self.__checkRequirements(pkg) check = DocFilesCheck() def test(): for (arg,exp) in ((['a'],['a']), ([], []), (['a','b'], ['a', 'b']), (['a','b', 'c', 'd'], ['a', 'b', 'c', 'd']), (['a','>', '0'], ['a']), (['a','>', '0', 'b'], ['a', 'b']), (['a','>', '0', 'b', '>', '0'], ['a', 'b']), ): assert(_stripVersionedDeps(arg) == exp) x = _RequireRange() x.update("<", "20") x.update(">=", "5") x.update(">", "5") x.update(">", "5") x.update(">", "3") x.update(">=", "7") x.update(">=", "5") y = _RequireRange().update("<", "7") print "%s - %s = %s" % (x, y, map(lambda x: str(x), x.subtract(y))) y = _RequireRange().update("<=", "7") print "%s - %s = %s" % (x, y, map(lambda x: str(x), x.subtract(y))) y = _RequireRange().update("<", "10") print "%s - %s = %s" % (x, y, map(lambda x: str(x), x.subtract(y))) y = _RequireRange().update("<=", "10") print "%s - %s = %s" % (x, y, map(lambda x: str(x), x.subtract(y))) y = _RequireRange().update("<=", "2") print "%s - %s = %s" % (x, y, map(lambda x: str(x), x.subtract(y))) y = _RequireRange().update(">", "20") print "%s - %s = %s" % (x, y, map(lambda x: str(x), x.subtract(y))) y = _RequireRange().update(">=", "20") print "%s - %s = %s" % (x, y, map(lambda x: str(x), x.subtract(y))) y = _RequireRange().update(">", "15") print "%s - %s = %s" % (x, y, map(lambda x: str(x), x.subtract(y))) y = _RequireRange().update(">=", "15") print "%s - %s = %s" % (x, y, map(lambda x: str(x), x.subtract(y))) y = _RequireRange().update("<", "25") print "%s - %s = %s" % (x, y, map(lambda x: str(x), x.subtract(y))) y = _RequireRange().update("<", "19").update(">", "10") print "%s - %s = %s" % (x, y, map(lambda x: str(x), x.subtract(y))) y = _RequireRange() print "%s - %s = %s" % (x, y, map(lambda x: str(x), x.subtract(y))) _ARange.merge([y,x,y]) print map(lambda x: str(x), x.subtract(_EVR("72"))) print x.contains(_EVR("0:20")) x.update("=", "10") print x print map(lambda x: str(x), x.subtract(_EVR("10"))) #test()