1 # vi: encoding=utf-8 ts=8 sts=4 sw=4 et
3 from __future__ import print_function
5 import os, config, string, urllib, re, rpm
6 from common import fileexists, noarchcachedir
7 from baseftptree import BasePkg, BaseFtpTree
8 from sign import is_signed
13 class SomeError(Exception):
18 return "An Error occured!"
22 print("%d error(s) encountered... aborting" % errnum)
36 def rm(file, test = False):
38 if not os.path.exists(file):
39 pinfo("TEST os.remove(%s): file doesn't exists" % file)
44 pinfo("os.remove(%s): %s" % (file, e))
47 def mv(src, dst, test = False):
49 fdst = dst + '/' + src.split('/')[-1]
51 if not os.path.exists(fsrc):
52 pinfo("TEST os.rename(%s, %s): source doesn't exists" % (fsrc, fdst))
53 if not os.path.exists(dst):
54 pinfo("TEST destination doesn't exist: %s" % dst)
59 pinfo("os.rename(%s, %s): %s" % (fsrc, fdst, e))
63 def __init__(self, nvr, tree):
64 BasePkg.__init__(self, nvr, tree)
65 self.name = string.join(nvr.split('-')[:-2], '-')
66 self.version = nvr.split('-')[-2]
67 self.release = nvr.split('-')[-1]
68 self.marked4removal = False
69 self.marked4moving = False
70 self.marked4movingpool = []
74 def __cmp__(self, pkg):
75 if self.name > pkg.name:
77 elif self.name < pkg.name:
80 return rpm.labelCompare(('0', self.version, self.release),
81 ('0', pkg.version, pkg.release))
84 # unfortunately can't do new Pkg(NVR), and have no "tree" in this pkg context
85 # so this static function
86 def is_debuginfo(self, nvr):
88 returns true if NVR is debuginfo package and separate debuginfo is enabled
90 if not config.separate_debuginfo:
92 pkg = nvr.split('-')[:-2]
93 return pkg[-1] == 'debuginfo'
95 def is_sourcefile(self, file):
97 returns true if file is source package
99 return file[-8:] == '.src.rpm'
101 # returns true if package build is integer
102 def is_release(self):
104 To account Release tags with subver macros, we consider integer release
105 if it contains odd number of dots:
109 0.%{subver}.%{rel}, %{rel} = 1 -> 0.20010.1 -> True
110 0.%{subver}.%{rel}, %{rel} = 0.1 -> 0.20010.0.1 -> False
112 return self.release.count('.') % 2 == 0
114 def mark4moving(self):
115 if not self.marked4moving:
116 # Only one pkg in this pool can be marked for moving
117 for pkg in self.marked4movingpool:
119 self.tree.marked4moving.append(self)
120 self.marked4moving=True
122 def unmark4moving(self):
123 if self.marked4moving:
124 self.tree.marked4moving.remove(self)
125 self.marked4moving=False
127 def mark4removal(self):
128 if not self.marked4removal:
129 self.tree.marked4removal.append(self)
130 self.marked4removal=True
132 def error(self, msg):
133 self.errors.append(msg)
135 perror('%s %s' % (self.nvr, msg))
137 def warning(self, msg):
138 self.warnings.append(msg)
140 pwarning('%s %s' % (self.nvr, msg))
142 def load(self, content=None):
143 BasePkg.load(self, content)
144 if self.info.has_key('move'):
148 f = open(self.tree.basedir+'/SRPMS/.metadata/'+self.nvr+'.src.rpm.info', 'w')
149 for bid in self.build.keys():
150 f.write("info:build:%s:requester:%s\ninfo:build:%s:requester_email:%s\n" % (bid, self.build[bid].requester, bid, self.build[bid].requester_email))
151 for key in self.info.keys():
152 f.write("info:%s:%s\n" % (key, string.join(self.info[key], ':')))
153 for arch in self.files.keys():
154 for rpm in self.files[arch]:
155 f.write("file:%s:%s\n" % (arch, rpm))
157 def remove(self, test = False):
159 Remove package from ftp
161 for arch in self.files.keys():
162 for rpm in self.files[arch]:
163 if self.is_debuginfo(rpm):
164 rm(self.tree.basedir + '/' + arch + '/debuginfo/' + rpm, test)
166 rm(self.tree.basedir + '/' + arch + '/RPMS/' + rpm, test)
168 if fileexists(noarchcachedir + rpm + '.filelist'):
169 rm(noarchcachedir + rpm + '.filelist', test)
170 if fileexists(noarchcachedir + rpm + '.reqlist'):
171 rm(noarchcachedir + rpm + '.reqlist', test)
172 rm(self.tree.basedir + '/SRPMS/.metadata/' + self.nvr + '.src.rpm.info', test)
174 def rpmfiles(self, debugfiles = True, sourcefiles = True):
176 Return rpm files related to this package
179 for arch, rpms in self.files.items():
181 if self.is_debuginfo(nvr):
183 files.append(self.tree.basedir + '/' + arch + '/debuginfo/' + nvr)
185 if self.is_sourcefile(nvr):
187 files.append(self.tree.basedir + '/' + arch + '/RPMS/' + nvr)
189 files.append(self.tree.basedir + '/' + arch + '/RPMS/' + nvr)
194 Return obsoletes for all packages in Pkg:
196 {'php-geshi': set(['geshi'])}
201 ts.setVSFlags(rpm.RPMVSF_NODSAHEADER)
202 fdno = os.open(pkg, os.O_RDONLY)
203 hdr = ts.hdrFromFdno(fdno)
208 for rpmfile in self.rpmfiles():
209 if not os.path.exists(rpmfile):
211 hdr = rpmhdr(rpmfile)
212 if not hdr[rpm.RPMTAG_OBSOLETES]:
215 name = hdr[rpm.RPMTAG_NAME]
216 if not name in obsoletes:
217 obsoletes[name] = set()
219 for tag in hdr[rpm.RPMTAG_OBSOLETES]:
220 obsoletes[name].add(tag)
224 def move(self, dsttree, test = False):
225 if dsttree.has_key(self.nvr):
227 for arch in self.files.keys():
228 if arch in dsttree[self.nvr].files.keys():
232 pinfo("%sArch %s for %s is already present in dest tree; removing from srctree" % (msg, arch, self.nvr))
233 for rpm in self.files[arch]:
234 if self.is_debuginfo(rpm):
235 rm(self.tree.basedir + '/' + arch + '/debuginfo/' + rpm, test)
237 rm(self.tree.basedir + '/' + arch + '/RPMS/' + rpm, test)
240 dsttree[self.nvr].files[arch] = self.files[arch]
241 for rpm in self.files[arch]:
242 if self.is_debuginfo(rpm):
243 mv(self.tree.basedir + '/' + arch + '/debuginfo/' + rpm, dsttree.basedir + '/' + arch + '/debuginfo/', test)
245 mv(self.tree.basedir + '/' + arch + '/RPMS/' + rpm, dsttree.basedir + '/' + arch + '/RPMS/', test)
246 if not test and movedany:
247 for bid in self.build.keys():
248 dsttree[self.nvr].build[bid] = self.build[bid]
249 dsttree[self.nvr].writeinfo()
250 rm(self.tree.basedir + '/SRPMS/.metadata/' + self.nvr + '.src.rpm.info', test)
253 for arch in self.files.keys():
254 for rpm in self.files[arch]:
255 if self.is_debuginfo(rpm):
256 mv(self.tree.basedir + '/' + arch + '/debuginfo/' + rpm, dsttree.basedir + '/' + arch + '/debuginfo/', test)
258 mv(self.tree.basedir + '/' + arch + '/RPMS/' + rpm, dsttree.basedir + '/' + arch + '/RPMS/', test)
261 mv(self.tree.basedir + '/SRPMS/.metadata/' + self.nvr + '.src.rpm.info', dsttree.basedir + '/SRPMS/.metadata/', test)
263 class FtpTree(BaseFtpTree):
264 def __init__(self, tree, loadall=False):
265 BaseFtpTree.__init__(self, tree)
267 self.marked4removal = []
268 self.marked4moving = []
270 self.__loadpkgnames()
272 for pkgname in self.pkgnames:
273 self.loadedpkgs[pkgname] = Pkg(pkgname, self)
275 self.do_checkbuild = True
277 def __getitem__(self, key):
278 if self.loadedpkgs.has_key(key):
279 return self.loadedpkgs[key]
280 elif key in self.pkgnames:
282 self.loadedpkgs[key]=pkg
287 def has_key(self, key):
288 if key in self.pkgnames:
297 return self.loadedpkgs.values()
299 def checktree(self, dsttree):
300 self.__checkbuild(self.loadedpkgs.values())
301 self.__checkarchs(dsttree, self.loadedpkgs.values())
303 def testmove(self, dsttree, archivetree = None):
304 self.__checkbuild(self.marked4moving)
305 self.__checkarchs(dsttree, self.marked4moving)
306 if not dsttree.treename.count("archive"):
307 self.__checkduplicates(self.marked4moving)
309 self.__checksigns(dsttree, self.marked4moving, test = True)
310 self.__checkforobsoletes(dsttree, self.marked4moving, test = True)
311 self.__checkforrelease(dsttree, self.marked4moving, test = True)
313 if not self.treename.count("archive"):
314 self.__rmolderfromsrc(test = True)
315 if not dsttree.treename.count("archive"):
316 self.__rmotherfromdst(dsttree, test = True, archivetree = archivetree)
318 for pkg in self.marked4moving:
319 pkg.move(dsttree, test = True)
321 def movepkgs(self, dsttree, archivetree = None):
322 if self.do_checkbuild:
323 self.__checkbuild(self.marked4moving)
326 self.__checkarchs(dsttree, self.marked4moving)
329 self.__checksigns(dsttree, self.marked4moving)
332 if not self.treename.count("archive"):
333 self.__rmolderfromsrc()
334 if not dsttree.treename.count("archive"):
335 self.__rmotherfromdst(dsttree, archivetree = archivetree)
337 for pkg in self.marked4moving:
340 def rpmfiles(self, debugfiles = True, sourcefiles = True):
341 if self.do_checkbuild:
342 self.__checkbuild(self.marked4moving)
345 for pkg in self.marked4moving:
346 files += pkg.rpmfiles(debugfiles = debugfiles, sourcefiles = sourcefiles)
349 def removepkgs(self):
350 if self.do_checkbuild:
351 self.__checkbuild(self.marked4removal)
353 for pkg in self.marked4removal:
356 def mark4removal(self, wannabepkgs):
357 self.__mark4something(wannabepkgs, Pkg.mark4removal)
359 def mark4moving(self, wannabepkgs):
360 self.__mark4something(wannabepkgs, Pkg.mark4moving)
362 # Internal functions below
363 def __arch_stringify(self, list):
365 dist = config.ftp_dist;
367 ret.append(dist + '-' + arch)
370 def __loadpkgnames(self):
371 def checkfiletype(name):
372 if name[-13:]=='.src.rpm.info':
376 list = filter(checkfiletype, os.listdir(self.basedir+'/SRPMS/.metadata'))
377 self.pkgnames = map((lambda x: x[:-13]), list)
379 def __mark4something(self, wannabepkgs, markfunction):
380 def chopoffextension(pkg):
381 found = pkg.find('.src.rpm')
387 for wannabepkg in wannabepkgs:
388 pkgname = chopoffextension(wannabepkg)
389 if pkgname in self.pkgnames:
390 if not pkgname in self.loadedpkgs.keys():
391 self.loadedpkgs[pkgname]=Pkg(pkgname, self)
392 markfunction(self.loadedpkgs[pkgname])
394 perror('%s not found in source tree' % pkgname)
397 def __checkbuild(self, marked):
399 Checks queue file if all arches are built
401 Reads config.builderqueue to grab the info
403 f = urllib.urlopen(config.builderqueue)
405 reid = re.compile(r'^.*id=(.*) pri.*$')
406 regb = re.compile(r'^group:.*$|builders:.*$', re.M)
407 for i in re.findall(regb, f.read()):
409 id = reid.sub(r'\1', i)
412 requests[id] = requests[id] + i
416 for bid in pkg.build.keys():
417 if requests.has_key(bid) and not requests[bid].find('?') == -1:
418 pkg.error("(buildid %s) building not finished" % bid)
420 def __checkarchs(self, dsttree, marked):
422 Checks marked pkgs it is built on all archs.
425 if len(pkg.files.keys()) <= 1:
426 pkg.error('has only src.rpm built')
428 otherpkgnames = self.__find_other_pkgs(pkg, dsttree)
430 # check if we're not removing some archs
434 for somepkg in otherpkgnames:
435 curarchs.extend(Pkg(somepkg, dsttree).files.keys())
436 for arch in curarchs:
437 if arch not in pkg.files.keys():
438 missingarchs.append(arch)
440 pkg.error('moving would remove archs: %s' % self.__arch_stringify(missingarchs))
442 # warn if a package isn't built for all archs
444 ftp_archs_num = len(config.ftp_archs) + 1
445 if (config.separate_noarch and 'noarch' in pkg.files.keys()):
446 # ftp_archs + SRPMS + noarch subpackages
448 # plain simple noarch package
449 if (len(pkg.files.keys()) == 2):
452 if len(pkg.files.keys()) != ftp_archs_num:
454 for arch in config.ftp_archs:
455 if arch not in pkg.files.keys():
456 missingarchs.append(arch)
457 pkg.warning('not built for archs: %s' % self.__arch_stringify(missingarchs))
459 def __checkduplicates(self, marked):
461 Checks if marked packages contain duplicate packages (with different versions)
464 olderpkgnames = self.__find_older_pkgs(pkg)
465 for i in olderpkgnames:
466 markednames = [str(x) for x in marked]
468 pkg.error('duplicate package: %s' % i)
470 def __rmolderfromsrc(self, test = False):
471 for pkg in self.marked4moving:
472 olderpkgnames = self.__find_older_pkgs(pkg)
473 for i in olderpkgnames:
474 Pkg(i, self).remove(test)
476 def __rmotherfromdst(self, dsttree, test = False, archivetree = None):
477 for pkg in self.marked4moving:
478 pkgnames = self.__find_other_pkgs(pkg, dsttree)
480 if archivetree == None:
481 Pkg(i, dsttree).remove(test)
483 Pkg(i, dsttree).move(archivetree, test = test)
485 # Used more than once filter functions
486 def __find_other_pkgs(self, pkg, tree):
487 escapedpkgname = pkg.name.replace('.', '\.').replace('+', '\+')
488 ziewre = re.compile(escapedpkgname + '-[^-]*-[^-]*$')
489 def filter_other_pkgs(x):
490 if ziewre.match(x) and not x == pkg.nvr:
494 return filter(filter_other_pkgs, tree.pkgnames)
496 def __find_older_pkgs(self, pkg):
497 def filter_older_pkgs(x):
499 rc = rpm.labelCompare(('0', pkg.version, pkg.release),
501 if rc == 1: # pkg > x
505 return filter(filter_older_pkgs, self.__find_other_pkgs(pkg, self))
507 def __checksigns(self, tree, pkgs, test = False):
509 Checks if pkgs in tree are all signed.
511 in case of test = true, error flag is set for unsigned packages
513 if not tree.treename in config.signed_trees:
518 for file in pkg.rpmfiles():
519 if not is_signed(file):
525 pkg.warning('%d files not signed' % unsigned)
527 pkg.error('%d files not signed' % unsigned)
529 def __checkforobsoletes(self, tree, pkgs, test = False):
531 Checks queue file if package obsoletes something in destination tree and suggest for removal.
533 Only NAME tag is compared, i.e virtual packages do not get reported.
539 def findbyname(name):
541 return '-'.join(nvr.split('-')[:-2]) == name
542 return filter(x, tree.pkgnames)
545 obsoletes = pkg.obsoletes()
549 for pn, setlist in obsoletes.items():
553 pkg.warning('obsoletes %s (via %s) in dest tree, perhaps you want rmpkg' % (p,pn))
555 def __checkforrelease(self, tree, pkgs, test = False):
557 Checks queue file if package release is non integer.
564 if not pkg.is_release():
565 pkg.warning('non-integer release: %s' % pkg.release)