#!/usr/bin/env python3 # Copyright 2011 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 import datetime import optparse import os.path import random import re import subprocess import xmlrpc.client from portage.package.ebuild.getmaskingstatus import getmaskingstatus from portage.xml.metadata import MetaDataXML import portage.versions from common import login if __name__ == "__main__": parser = optparse.OptionParser() parser.add_option("--arch", dest="arch", action="append", help="Gentoo arch to use, e.g. x86, amd64, ... Can be passed multiple times.") parser.add_option("--days", dest="days", type=int, default=30, help="Number of days in the tree after stabilization is possible.") parser.add_option("--repo", dest="repo", help="Path to portage CVS repository") parser.add_option("--category", dest="category", help="Portage category filter (default is all categories)") parser.add_option("--exclude", dest="exclude", default=".*(kde-base|sci|lisp|perl-core|virtual|gnome|ruby|x11|mono|dotnet|games|xfce|xfburn|mousepad|orage|xfbib|thunar|ristretto|pragha|xfmpc|parole|midori|gigolo|rodent|xfwm|girara|zathura|leechcraft).*", help="Regular expression for excluded packages.") parser.add_option("-o", "--output", dest="output_filename", default="stabilization-candidates.txt", help="Output filename for generated stabilization candidates list.") (options, args) = parser.parse_args() if not options.arch: parser.error("--arch option is required") if not options.repo: parser.error("--repo option is required") if args: parser.error("unrecognized command-line args") url = 'https://bugs.gentoo.org/xmlrpc.cgi' print('You will be prompted for your Gentoo Bugzilla username and password (%s).' % url) bugzilla = xmlrpc.client.ServerProxy(url) user, login_data = login(bugzilla) final_candidates = [] now = datetime.datetime.now() for cp in portage.portdb.cp_all(): if options.category and not cp.startswith(options.category + "/"): continue cvs_path = os.path.join(options.repo, cp) try: metadata = MetaDataXML(os.path.join(cvs_path, 'metadata.xml'), '/usr/portage/metadata/herds.xml') except IOError: continue maintainer_split = metadata.format_maintainer_string().split(' ', 1) maintainer = maintainer_split[0] if len(maintainer_split) > 1: other_maintainers = maintainer_split[1].split(',') else: other_maintainers = [] if options.exclude: if re.match(options.exclude, cp): continue if re.match(options.exclude, maintainer): continue skip = False for m in other_maintainers: if re.match(options.exclude, m): skip = True break if skip: continue best_stable = portage.versions.best(portage.portdb.match(cp)) if not best_stable: continue print('Working on %s...' % cp, end=' ') candidates = [] for cpv in portage.portdb.cp_list(cp): # Only consider higher versions than best stable. if portage.versions.pkgcmp(portage.versions.pkgsplit(cpv), portage.versions.pkgsplit(best_stable)) != 1: continue # Eliminate alpha, beta, pre, rc, and so on packages. is_unstable = False for suffix in portage.versions.endversion_keys: if ("_" + suffix) in portage.versions.pkgsplit(cpv)[1]: is_unstable = True break if is_unstable: continue # Eliminate 'live' packages. Obviously have some false positives, # but it'd be much worse to miss something. There are variations # like -r9999 or .9999 in the tree. if '99' in cpv: continue # Eliminate hard masked packages among others. if getmaskingstatus(cpv) not in [['~%s keyword' % arch] for arch in options.arch]: continue candidates.append(cpv) if not candidates: print('no candidates') continue candidates.sort(key=portage.versions.cpv_sort_key()) candidates.reverse() # Only consider the best version for stabilization. # It's usually better tested, and often maintainers refuse # to stabilize anything else, e.g. bug #391607. best_candidate = str(candidates[0]) pv = portage.versions.catsplit(best_candidate)[1] try: with open(os.path.join(options.repo, cp, 'ChangeLog')) as changelog_file: regex = '\*%s \((.*)\)' % re.escape(pv) match = re.search(regex, changelog_file.read()) if not match: print('error parsing ChangeLog') continue changelog_date = datetime.datetime.strptime(match.group(1), '%d %b %Y') if now - changelog_date < datetime.timedelta(days=options.days): print('not old enough') continue except IOError as e: print(e) continue keywords = portage.db["/"]["porttree"].dbapi.aux_get(best_candidate, ['KEYWORDS'])[0] missing_arch = False for arch in options.arch: if arch not in keywords: missing_arch = True break if missing_arch: print('not keyworded ~arch') continue # Do not risk trying to stabilize a package with known bugs. params = {} params['Bugzilla_token'] = login_data['token'] params['summary'] = cp bugs = [x for x in bugzilla.Bug.search(params)['bugs'] if x['is_open'] and x['severity'] not in ['enhancement', 'QA']] if bugs: print('has bugs') continue # Protection against filing a stabilization bug twice. params = {} params['Bugzilla_token'] = login_data['token'] params['summary'] = best_candidate bugs = bugzilla.Bug.search(params)['bugs'] if bugs: print('version has bugs') continue ebuild_name = portage.versions.catsplit(best_candidate)[1] + ".ebuild" ebuild_path = os.path.join(cvs_path, ebuild_name) manifest_path = os.path.join(cvs_path, 'Manifest') try: original_contents = open(ebuild_path).read() manifest_contents = open(manifest_path).read() except IOError as e: print(e) continue try: for arch in options.arch: subprocess.check_output(["ekeyword", arch, ebuild_name], cwd=cvs_path) subprocess.check_output(["repoman", "manifest"], cwd=cvs_path) subprocess.check_output(["repoman", "full"], cwd=cvs_path) except subprocess.CalledProcessError: print('repoman error') continue finally: f = open(ebuild_path, "w") f.write(original_contents) f.close() f = open(manifest_path, "w") f.write(manifest_contents) f.close() with open(options.output_filename, 'a') as f: f.write('# %s %s\n' % (maintainer, ', '.join(other_maintainers))) f.write('%s\n' % best_candidate) print((best_candidate, maintainer, other_maintainers)) params = {} params['Bugzilla_token'] = login_data['token'] bugzilla.User.logout(params)