2 # Copyright 1999-2006 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
6 # Next to do: dep syntax checking in mask files
7 # Then, check to make sure deps are satisfiable (to avoid "can't find match for" problems)
8 # that last one is tricky because multiple profiles need to be checked.
10 import errno, os, shutil, sys
11 if not hasattr(__builtins__, "set"):
12 from sets import Set as set
13 exename=os.path.basename(sys.argv[0])
16 allowed_filename_chars="a-zA-Z0-9._-+:"
17 allowed_filename_chars_set = {}
18 map(allowed_filename_chars_set.setdefault, map(chr, range(ord('a'), ord('z')+1)))
19 map(allowed_filename_chars_set.setdefault, map(chr, range(ord('A'), ord('Z')+1)))
20 map(allowed_filename_chars_set.setdefault, map(chr, range(ord('0'), ord('9')+1)))
21 map(allowed_filename_chars_set.setdefault, map(chr, map(ord, [".", "-", "_", "+", ":"])))
23 import signal,re,pickle,tempfile
25 os.environ["PORTAGE_LEGACY_GLOBALS"] = "false"
29 sys.path.insert(0, "/usr/lib/portage/pym")
31 del os.environ["PORTAGE_LEGACY_GLOBALS"]
33 import portage_checksum
36 portage_dep._dep_check_strict = True
37 import portage_exception
42 from portage_manifest import Manifest
43 from portage_exception import ParseError
44 from portage_exec import spawn
46 from output import bold, darkgreen, darkred, green, nocolor, red, turquoise, yellow
48 from commands import getstatusoutput
49 from fileinput import input
50 from grp import getgrnam
51 from stat import S_ISDIR, ST_CTIME, ST_GID, ST_MTIME
53 # A sane umask is needed for files that portage creates.
55 repoman_settings = portage.config(local_config=False,
56 config_incrementals=portage_const.INCREMENTALS)
57 repoman_settings.lock()
59 if repoman_settings.get("NOCOLOR", "").lower() in ("yes", "true") or \
60 not sys.stdout.isatty():
64 print exename+": "+txt
70 help(exitstatus=-1,helpfulness=0)
74 def exithandler(signum=None,frame=None):
75 sys.stderr.write("\n"+exename+": Interrupted; exiting...\n")
77 os.kill(0,signal.SIGKILL)
78 signal.signal(signal.SIGINT,exithandler)
80 shortmodes={"ci":"commit"}
82 "scan" : "Scan directory tree for QA issues (default)",
83 "fix" : "Fix simple QA issues (stray digests, missing digests)",
84 "full" : "Scan directory tree for QA issues (full listing)",
85 "help" : "Show this screen",
86 "commit" : "Scan directory tree for QA issues; if OK, commit via cvs",
87 "last" : "Remember report from last run",
88 "lfull" : "Remember report from last run (full listing)"
90 modes=modeshelp.keys()
92 "--commitmsg" : "Adds a commit message via the command line",
93 "--commitmsgfile" : "Adds a commit message from the specified file",
94 "--help" : "Show this screen",
95 "--ignore-arches" : "Ignore arch-specific failures (where arch != host)",
96 "--ignore-masked" : "Ignore masked packages (not allowed with commit mode)",
97 "--pretend" : "Don't commit or fix anything; just show what would be done",
98 "--quiet" : "Be less verbose about extraneous info",
99 "--verbose" : "Displays every package name while checking",
100 "--version" : "Show version info",
101 "--xmlparse" : "Forces the metadata.xml parse check to be carried out"
103 repoman_shortoptions={
105 "-i" : "--ignore-masked",
106 "-I" : "--ignore-arches",
107 "-m" : "--commitmsg",
108 "-M" : "--commitmsgfile",
115 repoman_shortoptions_rev=dict([(v,k) for (k,v) in repoman_shortoptions.items()])
116 options=repoman_options.keys()
119 "CVS/Entries.IO_error":"Attempting to commit, and an IO error was encountered access the Entries file",
120 "digest.partial":"Digest files do not contain all corresponding URI elements",
121 "digest.assumed":"Existing digest must be assumed correct (Package level only)",
122 "digestentry.unused":"Digest/Manifest entry has no matching SRC_URI entry",
123 "digest.fail":"Digest does not match the specified local file",
124 "digest.stray":"Digest files that do not have a corresponding ebuild",
125 "digest.missing":"Digest files that are missing (ebuild exists, digest doesn't)",
126 "digest.disjointed":"Digests not added to cvs when the matching ebuild has been added",
127 "digest.notadded":"Digests that exist but have not been added to cvs",
128 "digest.unmatch":"Digests which are incomplete (please check if your USE/ARCH includes all files)",
129 "ebuild.invalidname":"Ebuild files with a non-parseable or syntactically incorrect name (or using 2.1 versioning extensions)",
130 "ebuild.namenomatch":"Ebuild files that do not have the same name as their parent directory",
131 "changelog.missing":"Missing ChangeLog files",
132 "ebuild.disjointed":"Ebuilds not added to cvs when the matching digest has been added",
133 "ebuild.notadded":"Ebuilds that exist but have not been added to cvs",
134 "changelog.notadded":"ChangeLogs that exist but have not been added to cvs",
135 "filedir.missing":"Package lacks a files directory",
136 "file.executable":"Ebuilds, digests, metadata.xml, Manifest, and ChangeLog do note need the executable bit",
137 "file.size":"Files in the files directory must be under 20k",
138 "file.name":"File/dir name must be composed of only the following chars: %s " % allowed_filename_chars,
139 "file.UTF8":"File is not UTF8 compliant",
140 "KEYWORDS.missing":"Ebuilds that have a missing or empty KEYWORDS variable",
141 "KEYWORDS.stupid":"Ebuilds that use KEYWORDS=-* instead of package.mask",
142 "LICENSE.missing":"Ebuilds that have a missing or empty LICENSE variable",
143 "DESCRIPTION.missing":"Ebuilds that have a missing or empty DESCRIPTION variable",
144 "SLOT.missing":"Ebuilds that have a missing or empty SLOT variable",
145 "HOMEPAGE.missing":"Ebuilds that have a missing or empty HOMEPAGE variable",
146 "DEPEND.bad":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds)",
147 "RDEPEND.bad":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds)",
148 "PDEPEND.bad":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds)",
149 "DEPEND.badmasked":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds)",
150 "RDEPEND.badmasked":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds)",
151 "PDEPEND.badmasked":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds)",
152 "DEPEND.badindev":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds) in developing arch",
153 "RDEPEND.badindev":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds) in developing arch",
154 "PDEPEND.badindev":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds) in developing arch",
155 "DEPEND.badmaskedindev":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds) in developing arch",
156 "RDEPEND.badmaskedindev":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds) in developing arch",
157 "PDEPEND.badmaskedindev":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds) in developing arch",
158 "DEPEND.syntax":"Syntax error in DEPEND (usually an extra/missing space/parenthesis)",
159 "RDEPEND.syntax":"Syntax error in RDEPEND (usually an extra/missing space/parenthesis)",
160 "PDEPEND.syntax":"Syntax error in PDEPEND (usually an extra/missing space/parenthesis)",
161 "LICENSE.syntax":"Syntax error in LICENSE (usually an extra/missing space/parenthesis)",
162 "PROVIDE.syntax":"Syntax error in PROVIDE (usually an extra/missing space/parenthesis)",
163 "ebuild.syntax":"Error generating cache entry for ebuild; typically caused by ebuild syntax error",
164 "ebuild.output":"A simple sourcing of the ebuild produces output; this breaks ebuild policy.",
165 "ebuild.nesteddie":"Placing 'die' inside ( ) prints an error, but doesn't stop the ebuild.",
166 "variable.readonly":"Assigning a readonly variable",
167 "LIVEVCS.stable":"This ebuild is a live checkout from a VCS but has stable keywords.",
168 "IUSE.invalid":"This ebuild has a variable in IUSE that is not in the use.desc or use.local.desc file",
169 "LICENSE.invalid":"This ebuild is listing a license that doesnt exist in portages license/ dir.",
170 "KEYWORDS.invalid":"This ebuild contains KEYWORDS that are not listed in profiles/arch.list or for which no valid profile was found",
171 "ebuild.nostable":"There are no ebuilds that are marked as stable for your ARCH",
172 "ebuild.allmasked":"All ebuilds are masked for this package (Package level only)",
173 "ebuild.majorsyn":"This ebuild has a major syntax error that may cause the ebuild to fail partially or fully",
174 "ebuild.minorsyn":"This ebuild has a minor syntax error that contravenes gentoo coding style",
175 "ebuild.badheader":"This ebuild has a malformed header",
176 "metadata.missing":"Missing metadata.xml files",
177 "metadata.bad":"Bad metadata.xml files",
178 "virtual.versioned":"PROVIDE contains virtuals with versions",
179 "virtual.exists":"PROVIDE contains existing package names",
180 "virtual.unavailable":"PROVIDE contains a virtual which contains no profile default",
181 "usage.obsolete":"The ebuild makes use of an obsolete construct"
184 qacats = qahelp.keys()
189 "changelog.notadded",
198 "digestentry.unused",
199 "DEPEND.badmasked","RDEPEND.badmasked","PDEPEND.badmasked",
200 "DEPEND.badindev","RDEPEND.badindev","PDEPEND.badindev",
201 "DEPEND.badmaskedindev","RDEPEND.badmaskedindev","PDEPEND.badmaskedindev",
212 "virtual.unavailable",
217 missingvars=["KEYWORDS","LICENSE","DESCRIPTION","HOMEPAGE","SLOT"]
218 allvars=portage.auxdbkeys
220 commitmessagefile=None
221 for x in missingvars:
224 print "* missingvars values need to be added to qahelp ("+x+")"
233 print exename+" "+version
236 def help(exitstatus=1,helpfulness=1):
241 print green(exename+" "+version)
242 print " \"Quality is job zero.\""
243 print " Copyright 1999-2006 Gentoo Foundation"
244 print " Distributed under the terms of the GNU General Public License v2"
246 print bold(" Usage:"),turquoise(exename),"[",green("options"),"] [",green("mode"),"]"
248 print bold(" Modes:"),turquoise("scan (default)"),
250 print "|",turquoise(x),
253 print " "+green("Options".ljust(20)+" Description")
255 if repoman_shortoptions_rev.has_key(x):
256 shopt=repoman_shortoptions_rev[x]+", "+x
259 print " "+shopt.ljust(20),repoman_options[x]
261 print " "+green("Modes".ljust(20)+" Description")
263 print " "+x.ljust(20),modeshelp[x]
266 print " "+green("QA keyword".ljust(20)+" Description")
268 print " "+x.ljust(20),qahelp[x]
270 if (exitstatus != -1):
277 #Retrieve and unpickle stats and fails from saved files
278 savedf=open('/var/cache/edb/repo.stats','r')
279 stats = pickle.load(savedf)
281 savedf=open('/var/cache/edb/repo.fails','r')
282 fails = pickle.load(savedf)
284 except SystemExit, e:
285 raise # Need to propogate this
287 err("Error retrieving last repoman run data; exiting.")
289 #dofail will be set to 1 if we have failed in at least one non-warning category
291 #dowarn will be set to 1 if we tripped any warnings
293 #dofull will be set if we should print a "repoman full" informational message
297 print green("RepoMan remembers...")
302 if x not in qawarnings:
307 print " "+ x.ljust(20),
309 print green(`stats[x]`)
311 elif x in qawarnings:
312 print yellow(`stats[x]`)
314 print red(`stats[x]`)
326 print bold("Note: type \"repoman lfull\" for a complete listing of repomans last run.")
328 if dowarn and not dofail:
329 print green("RepoMan sez:"),"\"You only gave me a partial QA payment last time?\n I took it, but I wasn't happy.\""
331 print green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\""
339 while x < len(sys.argv):
340 if sys.argv[x] in shortmodes.keys():
341 sys.argv[x]=shortmodes[sys.argv[x]]
342 elif sys.argv[x] in repoman_shortoptions.keys():
343 sys.argv[x] = repoman_shortoptions[sys.argv[x]]
344 if sys.argv[x] in modes:
348 err("Please specify either \""+mymode+"\" or \""+sys.argv[x]+"\", but not both.")
349 elif sys.argv[x] in options:
351 if (optionx=="--commitmsg") and (len(sys.argv)>=(x+1)):
352 commitmessage=sys.argv[x+1]
354 elif (optionx=="--commitmsgfile") and (len(sys.argv)>=(x+1)):
355 commitmessagefile=sys.argv[x+1]
357 elif (optionx=="--verbose"):
359 elif (optionx=="--quiet"):
361 elif optionx not in myoptions:
362 myoptions.append(optionx)
364 err_help("\""+sys.argv[x]+"\" is not a valid mode or option.")
368 if mymode=="help" or ("--help" in myoptions):
370 if ("--version" in myoptions):
372 if mymode=="last" or (mymode=="lfull"):
374 if mymode == "commit":
375 while "--ignore-masked" in myoptions:
376 myoptions.remove("--ignore-masked")
378 from portage import normalize_path
381 if os.path.isdir("CVS"):
384 if mymode == "commit" and \
386 "--pretend" not in myoptions:
388 print darkgreen("Not in a CVS repository; enabling pretend mode.")
389 myoptions.append("--pretend");
392 def have_profile_dir(path, maxdepth=3):
393 while path != "/" and maxdepth:
394 if os.path.exists(path + "/profiles/package.mask"):
395 return normalize_path(path)
396 path = normalize_path(path + "/..")
402 if "PWD" in os.environ and os.environ["PWD"] != mydir and \
403 os.path.realpath(os.environ["PWD"]) == mydir:
404 # getcwd() returns the canonical path but that makes it hard for repoman to
405 # orient itself if the user has symlinks in their portage tree structure.
406 # We use os.environ["PWD"], if available, to get the non-canonical path of
407 # the current working directory (from the shell).
408 mydir = os.environ["PWD"]
409 mydir = normalize_path(mydir)
413 for overlay in repoman_settings["PORTDIR_OVERLAY"].split():
414 if overlay[-1] != "/":
416 if mydir.startswith(overlay):
417 portdir_overlay = overlay
418 subdir = mydir[len(overlay):]
419 if subdir and subdir[-1] != "/":
421 if have_profile_dir(mydir, subdir.count("/")):
422 portdir = portdir_overlay
425 if not portdir_overlay:
426 if (repoman_settings["PORTDIR"] + os.path.sep).startswith(mydir):
427 portdir_overlay = repoman_settings["PORTDIR"]
429 portdir_overlay = have_profile_dir(mydir)
430 portdir = portdir_overlay
432 if not portdir_overlay:
433 sys.stderr.write("Repoman is unable to determine PORTDIR or PORTDIR_OVERLAY from" + \
434 " the current\nworking directory.\n")
438 portdir = repoman_settings["PORTDIR"]
440 portdir = normalize_path(portdir)
441 portdir_overlay = normalize_path(portdir_overlay)
443 os.environ["PORTDIR"] = portdir
444 if portdir_overlay != portdir:
445 os.environ["PORTDIR_OVERLAY"] = portdir_overlay
447 os.environ["PORTDIR_OVERLAY"] = ""
450 print "\nSetting paths:"
451 print "PORTDIR = \""+os.environ["PORTDIR"]+"\""
452 print "PORTDIR_OVERLAY = \""+os.environ["PORTDIR_OVERLAY"]+"\""
454 # Now that PORTDIR_OVERLAY is properly overridden, create the portdb.
455 repoman_settings = portage.config(local_config=False,
456 config_incrementals=portage_const.INCREMENTALS)
457 trees = portage.create_trees()
458 trees["/"]["porttree"].settings = repoman_settings
459 portdb = trees["/"]["porttree"].dbapi
460 portdb.mysettings = repoman_settings
461 # dep_zapdeps looks at the vardbapi, but it shouldn't for repoman.
462 del trees["/"]["vartree"]
465 myreporoot = os.path.basename(portdir_overlay)
466 myreporoot += mydir[len(portdir_overlay):-1]
468 reposplit=myreporoot.split("/")
469 repolevel=len(reposplit)
471 # check if it's in $PORTDIR/$CATEGORY/$PN , otherwise bail if commiting.
472 # Reason for this is if they're trying to commit in just $FILESDIR/*, the Manifest needs updating.
473 # this check ensure that repoman knows where it is, and the manifest recommit is at least possible.
474 if mymode == "commit" and repolevel not in [1,2,3]:
475 print red("***")+" Commit attempts *must* be from within a cvs co, category, or package directory."
476 print red("***")+" Attempting to commit from a packages files directory will be blocked for instance."
477 print red("***")+" This is intended behaviour, to ensure the manifest is recommited for a package."
479 err("Unable to identify level we're commiting from for %s" % '/'.join(reposplit))
483 for x in range(0,repolevel-1):
489 err(mycat+" is not an official category. Skipping QA checks in this directory.\nPlease ensure that you add "+catdir+" to "+repodir+"/profiles/categories\nif it is a new category.")
490 if "--pretend" in myoptions:
491 print green("\nRepoMan does a once-over of the neighborhood...")
493 print green("\nRepoMan scours the neighborhood...")
495 def parse_use_local_desc(mylines, usedict=None):
496 """returns a dict of the form {cpv:set(flags)}"""
502 if not l or l.startswith("#"):
504 mysplit = l.split(None, 1)
507 mysplit = mysplit[0].split(":")
508 if len(mysplit) != 2:
509 raise ParseError("line %d: Malformed input: '%s'" % \
510 (lineno, l.rstrip("\n")))
511 usedict.setdefault(mysplit[0], set())
512 usedict[mysplit[0]].add(mysplit[1])
515 # retreive local USE list
518 f = open(os.path.join(portdir, "profiles", "use.local.desc"))
519 parse_use_local_desc(f, luselist)
521 except (IOError, OSError, ParseError), e:
522 print >> sys.stderr, str(e)
523 err("Couldn't read from use.local.desc")
525 if portdir_overlay != portdir:
526 filename = os.path.join(portdir_overlay, "profiles", "use.local.desc")
527 if os.path.exists(filename):
530 parse_use_local_desc(f, luselist)
532 except (IOError, OSError, ParseError), e:
533 print >> sys.stderr, str(e)
534 err("Couldn't read from '%s'" % filename)
537 # setup a uselist from portage
540 uselist=portage.grabfile(portdir+"/profiles/use.desc")
541 for l in range(0,len(uselist)):
542 uselist[l]=uselist[l].split()[0]
543 for var in repoman_settings["USE_EXPAND"].split():
544 vardescs = portage.grabfile(portdir+"/profiles/desc/"+var.lower()+".desc")
545 for l in range(0, len(vardescs)):
546 uselist.append(var.lower() + "_" + vardescs[l].split()[0])
547 except SystemExit, e:
548 raise # Need to propogate this
550 err("Couldn't read USE flags from use.desc")
552 # retrieve a list of current licenses in portage
553 liclist = set(portage.listdir(os.path.join(portdir, "licenses")))
555 err("Couldn't find licenses?")
556 if portdir_overlay != portdir:
557 liclist.update(portage.listdir(os.path.join(portdir_overlay, "licenses")))
559 # retrieve list of offical keywords
560 kwlist = set(portage.grabfile(os.path.join(portdir, "profiles", "arch.list")))
562 err("Couldn't read KEYWORDS from arch.list")
564 if portdir_overlay != portdir:
565 kwlist.update(portage.grabfile(
566 os.path.join(portdir_overlay, "profiles", "arch.list")))
570 #we are inside a category directory
572 if catdir not in repoman_settings.categories:
574 mydirlist=os.listdir(startdir)
576 if x == "CVS" or x.startswith("."):
578 if os.path.isdir(startdir+"/"+x):
579 scanlist.append(catdir+"/"+x)
581 for x in repoman_settings.categories:
582 if not os.path.isdir(startdir+"/"+x):
584 for y in os.listdir(startdir+"/"+x):
585 if y == "CVS" or y.startswith("."):
587 if os.path.isdir(startdir+"/"+x+"/"+y):
588 scanlist.append(x+"/"+y)
590 catdir = reposplit[-2]
591 if catdir not in repoman_settings.categories:
593 scanlist.append(catdir+"/"+reposplit[-1])
596 descfile=portdir+"/profiles/profiles.desc"
597 if os.path.exists(descfile):
598 for x in portage.grabfile(descfile):
603 print "wrong format: \""+red(x)+"\" in "+descfile
605 if not os.path.isdir(portdir+"/profiles/"+arch[1]):
606 print "Invalid "+arch[2]+" profile ("+arch[1]+") for arch "+arch[0]
608 if profiles.has_key(arch[0]):
609 profiles[arch[0]]+= [[arch[1], arch[2]]]
611 profiles[arch[0]] = [[arch[1], arch[2]]]
613 for x in repoman_settings.archlist():
616 if not profiles.has_key(x):
617 print red("\""+x+"\" doesn't have a valid profile listed in profiles.desc.")
618 print red("You need to either \"cvs update\" your profiles dir or follow this")
619 print red("up with the "+x+" team.")
622 print red("profiles.desc does not exist: "+descfile)
623 print red("You need to do \"cvs update\" in profiles dir.")
634 xmllint_capable = False
635 if getstatusoutput('which xmllint')[0] != 0:
636 print red("!!! xmllint not found. Can't check metadata.xml.\n")
637 if "--xmlparse" in myoptions or repolevel==3:
638 print red("!!!")+" sorry, xmllint is needed. failing\n"
641 #hardcoded paths/urls suck. :-/
645 # if it's been over a week since fetching (or the system clock is fscked), grab an updated copy of metadata.dtd
646 # clock is fscked or it's been a week. time to grab a new one.
647 ct=os.stat(portage.CACHE_PATH + '/metadata.dtd')[ST_CTIME]
648 if abs(time.time() - ct) > (60*60*24*7):
649 # don't trap the exception, we're watching for errno 2 (file not found), anything else is a bug.
654 except (OSError,IOError), e:
656 print red("!!!")+" caught exception '%s' for %s/metadata.dtd, bailing" % (str(e), portage.CACHE_PATH)
661 print green("***")+" the local copy of metadata.dtd needs to be refetched, doing that now"
664 if os.path.exists(repoman_settings["DISTDIR"]+'/metadata.dtd'):
665 os.remove(repoman_settings["DISTDIR"]+'/metadata.dtd')
666 val=portage.fetch(['http://www.gentoo.org/dtd/metadata.dtd'],repoman_settings,fetchonly=0, \
670 os.remove(portage.CACHE_PATH+'/metadata.dtd')
671 shutil.copy(repoman_settings["DISTDIR"]+'/metadata.dtd',portage.CACHE_PATH+'/metadata.dtd')
672 os.chown(portage.CACHE_PATH+'/metadata.dtd',os.getuid(),portage.portage_gid)
673 os.chmod(portage.CACHE_PATH+'/metadata.dtd',0664)
676 except SystemExit, e:
677 raise # Need to propogate this
680 print red("!!!")+" attempting to fetch 'http://www.gentoo.org/dtd/metadata.dtd', caught"
681 print red("!!!")+" exception '%s' though." % str(e)
684 print red("!!!")+" fetching new metadata.dtd failed, aborting"
686 #this can be problematic if xmllint changes their output
690 def x11_deprecation_check(depstr):
691 if depstr.find("virtual/x11") == -1:
693 depsplit = depstr.split()
696 for token in depsplit:
706 if token.find("virtual/x11") != -1 and (not ok_stack or not ok_stack[-1]):
712 arch_xmatch_caches = {}
714 #ebuilds and digests added to cvs respectively.
716 print "checking package " + x
719 catdir,pkgdir=x.split("/")
720 checkdir=repodir+"/"+x
721 checkdirlist=os.listdir(checkdir)
723 for y in checkdirlist:
724 if y[-7:]==".ebuild":
725 ebuildlist.append(y[:-7])
726 if y in ["Manifest","ChangeLog","metadata.xml"]:
727 if os.stat(checkdir+"/"+y)[0] & 0x0248:
728 stats["file.executable"] += 1
729 fails["file.executable"].append(checkdir+"/"+y)
732 for y in checkdirlist:
733 for c in y.strip(os.path.sep):
734 if c not in allowed_filename_chars_set:
735 stats["file.name"] += 1
736 fails["file.name"].append("%s/%s: char '%s'" % (checkdir, y, c))
739 if not (y in ("ChangeLog", "metadata.xml") or y.endswith(".ebuild")):
743 for l in codecs.open(checkdir+"/"+y, "r", "utf8"):
745 except UnicodeDecodeError, ue:
746 stats["file.UTF8"] += 1
747 s = ue.object[:ue.start]
751 s = s[s.rfind("\n") + 1:]
752 fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s))
756 mystat=os.stat(checkdir+"/files")[0]
757 if len(ebuildlist) and not S_ISDIR(mystat):
759 except SystemExit, e:
760 raise # Need to propogate this
762 stats["filedir.missing"] += 1
763 fails["filedir.missing"].append(checkdir)
766 myf=open(checkdir+"/CVS/Entries","r")
771 splitl=l[1:].split("/")
774 if splitl[0][-7:]==".ebuild":
775 eadded.append(splitl[0][:-7])
778 stats["CVS/Entries.IO_error"] += 1
779 fails["CVS/Entries.IO_error"].append(checkdir+"/CVS/Entries")
783 myf=open(checkdir+"/files/CVS/Entries","r")
788 splitl=l[1:].split("/")
791 if splitl[0][:7]=="digest-":
792 dadded.append(splitl[0][7:])
795 stats["CVS/Entries.IO_error"] += 1
796 fails["CVS/Entries.IO_error"].append(checkdir+"/files/CVS/Entries")
799 if mymode in ("fix", "commit") and \
800 "--pretend" not in myoptions:
801 repoman_settings["O"] = checkdir
802 if not portage.digestgen([], repoman_settings, myportdb=portdb):
803 print "Unable to generate manifest."
806 mf = Manifest(checkdir, repoman_settings["DISTDIR"])
807 mydigests=mf.getTypeDigests("DIST")
809 if os.path.exists(checkdir+"/files"):
810 filesdirlist=os.listdir(checkdir+"/files")
811 for y in filesdirlist:
813 if y[7:] not in dadded:
814 #digest not added to cvs
815 stats["digest.notadded"]=stats["digest.notadded"]+1
816 fails["digest.notadded"].append(x+"/files/"+y)
818 stats["digest.disjointed"]=stats["digest.disjointed"]+1
819 fails["digest.disjointed"].append(x+"/files/"+y)
821 if os.stat(checkdir+"/files/"+y)[0] & 0x0248:
822 stats["file.executable"] += 1
823 fails["file.executable"].append(x+"/files/"+y)
825 mykey = catdir + "/" + y[7:]
826 if y[7:] not in ebuildlist:
829 if "--pretend" in myoptions:
830 print "(cd "+repodir+"/"+x+"/files; cvs rm -f "+y+")"
832 os.system("(cd "+repodir+"/"+x+"/files; cvs rm -f "+y+")")
834 stats["digest.stray"]=stats["digest.stray"]+1
835 fails["digest.stray"].append(x+"/files/"+y)
838 myuris, myfiles = portdb.getfetchlist(mykey, all=True)
842 myubn = os.path.basename(myu)
843 if myubn not in uri_dict:
844 uri_dict[myubn] = [myu]
846 uri_dict[myubn] += [myu]
849 myff = repoman_settings["DISTDIR"] + "/" + myf
850 if not mydigests.has_key(myf):
851 uri_settings = portage.config(clone=repoman_settings)
853 if not portage.fetch(uri_dict[myf], uri_settings):
854 stats["digest.unmatch"] += 1
855 fails["digest.unmatch"].append(y+"::"+myf)
857 eb_name = portdb.findname2(mykey)[0]
858 portage.doebuild(eb_name, "digest", "/",
859 uri_settings, tree="porttree",
862 stats["digest.partial"] += 1
863 fails["digest.partial"].append(y+"::"+myf)
864 elif "assume-digests" not in repoman_settings.features:
865 if os.path.exists(myff):
866 if not portage_checksum.verify_all(myff, mydigests[myf]):
867 stats["digest.fail"] += 1
868 fails["digest.fail"].append(y+"::"+myf)
870 stats["digest.assumed"] += 1
871 fails["digest.assumed"].append(y+"::"+myf)
873 # recurse through files directory
874 # use filesdirlist as a stack, appending directories as needed so people can't hide > 20k files in a subdirectory.
876 y = filesdirlist.pop(0)
878 mystat = os.stat(checkdir+"/files/"+y)
881 # don't worry about it. it likely was removed via fix above.
885 if S_ISDIR(mystat.st_mode):
888 for z in os.listdir(checkdir+"/files/"+y):
891 filesdirlist.append(y+"/"+z)
892 # current policy is no files over 20k, this is the check.
893 elif mystat.st_size > 20480:
894 stats["file.size"] += 1
895 fails["file.size"].append("("+ str(mystat.st_size/1024) + "K) "+x+"/files/"+y)
897 for c in os.path.basename(y.rstrip(os.path.sep)):
898 if c not in allowed_filename_chars_set:
899 stats["file.name"] += 1
900 fails["file.name"].append("%s/files/%s: char '%s'" % (checkdir, y, c))
903 fetchlist_dict = portage.FetchlistDict(checkdir, repoman_settings, portdb)
905 for myfiles in fetchlist_dict.itervalues():
906 myfiles_all.extend(myfiles)
909 myfiles_all = set(myfiles_all)
910 for entry in mydigests:
911 if entry not in myfiles_all:
912 stats["digestentry.unused"] += 1
913 fails["digestentry.unused"].append(checkdir+"::"+entry)
914 del mydigests, myfiles_all
916 if "ChangeLog" not in checkdirlist:
917 stats["changelog.missing"]+=1
918 fails["changelog.missing"].append(x+"/ChangeLog")
920 #metadata.xml file check
921 if "metadata.xml" not in checkdirlist:
922 stats["metadata.missing"]+=1
923 fails["metadata.missing"].append(x+"/metadata.xml")
924 #metadata.xml parse check
926 #Only carry out if in package directory or check forced
928 st=getstatusoutput("xmllint --nonet --noout --dtdvalid %s/metadata.dtd %s/metadata.xml" % (portage.CACHE_PATH, checkdir))
930 for z in st[1].split("\n"):
932 stats["metadata.bad"]+=1
933 fails["metadata.bad"].append(x+"/metadata.xml")
938 if os.stat(checkdir+"/"+y+".ebuild")[0] & 0x0248:
939 stats["file.executable"] += 1
940 fails["file.executable"].append(x+"/"+y+".ebuild")
942 #ebuild not added to cvs
943 stats["ebuild.notadded"]=stats["ebuild.notadded"]+1
944 fails["ebuild.notadded"].append(x+"/"+y+".ebuild")
946 stats["ebuild.disjointed"]=stats["ebuild.disjointed"]+1
947 fails["ebuild.disjointed"].append(x+"/"+y+".ebuild")
948 if not os.path.exists(checkdir+"/files/digest-"+y):
950 if "--pretend" in myoptions:
951 print "You will need to run:"
952 print " /usr/bin/ebuild "+repodir+"/"+x+"/"+y+".ebuild digest"
954 retval=os.system("/usr/bin/ebuild "+repodir+"/"+x+"/"+y+".ebuild digest")
956 print "!!! Exiting on ebuild digest (shell) error code:",retval
959 stats["digest.missing"]=stats["digest.missing"]+1
960 fails["digest.missing"].append(x+"/files/digest-"+y)
961 myesplit=portage.pkgsplit(y)
962 if myesplit is None or myesplit[0] != x.split("/")[-1]:
963 stats["ebuild.invalidname"]=stats["ebuild.invalidname"]+1
964 fails["ebuild.invalidname"].append(x+"/"+y+".ebuild")
966 elif myesplit[0]!=pkgdir:
967 print pkgdir,myesplit[0]
968 stats["ebuild.namenomatch"]=stats["ebuild.namenomatch"]+1
969 fails["ebuild.namenomatch"].append(x+"/"+y+".ebuild")
973 zip(allvars, portdb.aux_get(os.path.join(catdir, y), allvars)))
975 stats["ebuild.syntax"]=stats["ebuild.syntax"]+1
976 fails["ebuild.syntax"].append(x+"/"+y+".ebuild")
979 stats["ebuild.output"]=stats["ebuild.output"]+1
980 fails["ebuild.output"].append(x+"/"+y+".ebuild")
983 # Test for negative logic and bad words in the RESTRICT var.
984 #for x in myaux[allvars.index("RESTRICT")].split():
985 # if x.startswith("no"):
986 # print "Bad RESTRICT value: %s" % x
988 myaux["PROVIDE"] = portage_dep.use_reduce(portage_dep.paren_reduce(myaux["PROVIDE"]), matchall=1)
989 myaux["PROVIDE"] = " ".join(portage.flatten(myaux["PROVIDE"]))
990 for myprovide in myaux["PROVIDE"].split():
991 prov_cp = portage.dep_getkey(myprovide)
992 if prov_cp != myprovide:
993 stats["virtual.versioned"]+=1
994 fails["virtual.versioned"].append(x+"/"+y+".ebuild: "+myprovide)
995 prov_pkg = portage.dep_getkey(
996 portage.best(portdb.xmatch("match-all", prov_cp)))
997 if prov_cp == prov_pkg:
998 stats["virtual.exists"]+=1
999 fails["virtual.exists"].append(x+"/"+y+".ebuild: "+prov_cp)
1001 for pos in range(0,len(missingvars)):
1002 if not myaux[missingvars[pos]]:
1003 myqakey=missingvars[pos]+".missing"
1004 stats[myqakey]=stats[myqakey]+1
1005 fails[myqakey].append(x+"/"+y+".ebuild")
1007 # KEYWORDS="-*" is a stupid replacement for package.mask and screws general KEYWORDS semantics
1008 if "-*" in myaux["KEYWORDS"].split():
1010 for kw in myaux["KEYWORDS"].split():
1016 stats["KEYWORDS.stupid"] += 1
1017 fails["KEYWORDS.stupid"].append(x+"/"+y+".ebuild")
1020 Ebuilds that inherit a "Live" eclasss (darcs,subversion,git,cvs,etc..) should
1021 not be allowed to be marked stable
1023 if set(["darcs","cvs","subversion","git"]).intersection(
1024 myaux["INHERITED"].split()):
1025 bad_stable_keywords = []
1026 for keyword in myaux["KEYWORDS"].split():
1027 if not keyword.startswith("~") and \
1028 not keyword.startswith("-"):
1029 bad_stable_keywords.append(keyword)
1031 if bad_stable_keywords:
1032 stats["LIVEVCS.stable"] += 1
1033 fails["LIVEVCS.stable"].append(
1034 x + "/" + y + ".ebuild with stable keywords:%s " % \
1035 bad_stable_keywords)
1036 del bad_stable_keywords
1038 if "--ignore-arches" in myoptions:
1039 arches = [[repoman_settings["ARCH"], repoman_settings["ARCH"],
1040 repoman_settings["ACCEPT_KEYWORDS"].split()]]
1043 for keyword in myaux["KEYWORDS"].split():
1044 if (keyword[0]=="-"):
1046 elif (keyword[0]=="~"):
1047 arches.append([keyword, keyword[1:], [keyword[1:], keyword]])
1049 arches.append([keyword, keyword, [keyword]])
1052 baddepsyntax = False
1053 badlicsyntax = False
1054 badprovsyntax = False
1055 catpkg = catdir+"/"+y
1056 myiuse = set(repoman_settings.archlist())
1057 for myflag in myaux["IUSE"].split():
1058 if myflag.startswith("+"):
1062 operator_tokens = set(["||", "(", ")"])
1063 type_list, badsyntax = [], []
1064 for mytype in ("DEPEND", "RDEPEND", "PDEPEND", "LICENSE", "PROVIDE"):
1065 mydepstr = myaux[mytype]
1067 if mydepstr.find(" ?") != -1:
1068 badsyntax.append("'?' preceded by space")
1071 # Missing closing parenthesis will result in a ValueError
1072 mydeplist = portage_dep.paren_reduce(mydepstr)
1073 # Missing opening parenthesis will result in a final "" element
1074 if "" in mydeplist or "(" in mydeplist:
1077 badsyntax.append("parenthesis mismatch")
1081 portage_dep.use_reduce(mydeplist, excludeall=myiuse)
1082 except portage_exception.InvalidDependString, e:
1083 badsyntax.append(str(e))
1085 for token in operator_tokens:
1086 if mydepstr.startswith(token+" "):
1087 myteststr = mydepstr[len(token):]
1089 myteststr = mydepstr
1090 if myteststr.endswith(" "+token):
1091 myteststr = myteststr[:-len(token)]
1092 while myteststr.find(" "+token+" ") != -1:
1093 myteststr = " ".join(myteststr.split(" "+token+" ", 1))
1094 if myteststr.find(token) != -1:
1095 badsyntax.append("'%s' not separated by space" % (token))
1098 if mytype in ("DEPEND", "RDEPEND", "PDEPEND"):
1099 for token in mydepstr.split():
1100 if token in operator_tokens or \
1101 token.endswith("?"):
1103 if not portage.isvalidatom(token, allow_blockers=True) or \
1104 ":" in token and myaux["EAPI"] == "0":
1105 badsyntax.append("'%s' not a valid atom" % token)
1107 type_list.extend([mytype] * (len(badsyntax) - len(type_list)))
1109 for m,b in zip(type_list, badsyntax):
1110 stats[m+".syntax"] += 1
1111 fails[m+".syntax"].append(catpkg+".ebuild "+m+": "+b)
1113 badlicsyntax = len(filter(lambda x:x=="LICENSE", type_list))
1114 badprovsyntax = len(filter(lambda x:x=="PROVIDE", type_list))
1115 baddepsyntax = len(type_list) != badlicsyntax + badprovsyntax
1116 badlicsyntax = badlicsyntax > 0
1117 badprovsyntax = badprovsyntax > 0
1119 if not baddepsyntax:
1120 if x11_deprecation_check(" ".join([myaux["DEPEND"], myaux["RDEPEND"], myaux["PDEPEND"]])):
1121 stats["usage.obsolete"] += 1
1122 fails["usage.obsolete"].append("%s/%s.ebuild: not migrated to modular X" % (x, y))
1124 for keyword,arch,groups in arches:
1126 if not profiles.has_key(arch):
1127 # A missing profile will create an error further down
1128 # during the KEYWORDS verification.
1131 for prof in profiles[arch]:
1133 profdir = portdir+"/profiles/"+prof[0]
1135 if prof[0] in arch_caches:
1136 dep_settings = arch_caches[prof[0]]
1138 dep_settings = portage.config(
1139 config_profile_path=profdir,
1140 config_incrementals=portage_const.INCREMENTALS,
1142 arch_caches[prof[0]] = dep_settings
1145 # Protect ACCEPT_KEYWORDS from config.regenerate()
1147 dep_settings.incrementals.remove("ACCEPT_KEYWORDS")
1151 xmatch_cache_key = (prof[0], tuple(groups))
1152 xcache = arch_xmatch_caches.get(xmatch_cache_key)
1156 xcache = portdb.xcache
1157 arch_xmatch_caches[xmatch_cache_key] = xcache
1159 trees["/"]["porttree"].settings = dep_settings
1160 portdb.mysettings = dep_settings
1161 portdb.xcache = xcache
1162 # for package.use.mask support inside dep_check
1163 dep_settings.setcpv("/".join((catdir, y)))
1164 dep_settings["ACCEPT_KEYWORDS"] = " ".join(groups)
1165 # just in case, prevent config.reset() from nuking these.
1166 dep_settings.backup_changes("ACCEPT_KEYWORDS")
1168 for myprovide in myaux["PROVIDE"].split():
1169 prov_cp = portage.dep_getkey(myprovide)
1170 if prov_cp not in dep_settings.getvirtuals():
1171 stats["virtual.unavailable"]+=1
1172 fails["virtual.unavailable"].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+prov_cp)
1174 if not baddepsyntax:
1175 ismasked = os.path.join(catdir, y) not in \
1176 portdb.xmatch("list-visible", x)
1178 if "--ignore-masked" in myoptions:
1180 #we are testing deps for a masked package; give it some lee-way
1182 matchmode="match-all"
1185 matchmode="match-visible"
1187 if prof[1] == "dev":
1188 suffix=suffix+"indev"
1190 for mytype,mypos in [["DEPEND",len(missingvars)],["RDEPEND",len(missingvars)+1],["PDEPEND",len(missingvars)+2]]:
1192 mykey=mytype+".bad"+suffix
1193 myvalue = myaux[mytype]
1197 mydep = portage.dep_check(myvalue, portdb,
1198 dep_settings, use="all", mode=matchmode,
1201 stats[mykey]=stats[mykey]+1
1202 fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(e[0]))
1207 #we have some unsolvable deps
1208 #remove ! deps, which always show up as unsatisfiable
1210 while d<len(mydep[1]):
1211 if mydep[1][d][0]=="!":
1215 #if we emptied out our list, continue:
1218 stats[mykey]=stats[mykey]+1
1219 fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(mydep[1]))
1221 stats[mykey]=stats[mykey]+1
1222 fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(mydep[1]))
1224 # this check needs work, it won't catch (\ndie)
1225 if not os.system("egrep '^[^#]*\([^)]*\<die\>' "+checkdir+"/"+y+".ebuild >/dev/null 2>&1"):
1226 stats["ebuild.nesteddie"]=stats["ebuild.nesteddie"]+1
1227 fails["ebuild.nesteddie"].append(x+"/"+y+".ebuild")
1228 # uselist checks - global
1231 for myflag in myaux["IUSE"].split():
1232 if myflag.startswith("+"):
1233 default_use.append(myflag)
1235 myuse.append(myflag)
1236 for mypos in range(len(myuse)-1,-1,-1):
1237 if myuse[mypos] and (myuse[mypos] in uselist):
1239 # uselist checks - local
1240 mykey = portage.dep_getkey(catpkg)
1241 if luselist.has_key(mykey):
1242 for mypos in range(len(myuse)-1,-1,-1):
1243 if myuse[mypos] and (myuse[mypos] in luselist[mykey]):
1245 if default_use and myaux["EAPI"] == "0":
1246 myuse += default_use
1247 for mypos in range(len(myuse)):
1248 stats["IUSE.invalid"]=stats["IUSE.invalid"]+1
1249 fails["IUSE.invalid"].append(x+"/"+y+".ebuild: %s" % myuse[mypos])
1252 if not badlicsyntax:
1253 myuse = myaux["LICENSE"]
1254 # Parse the LICENSE variable, remove USE conditions and
1256 myuse=portage_dep.use_reduce(portage_dep.paren_reduce(myuse), matchall=1)
1257 myuse=portage.flatten(myuse)
1258 # Check each entry to ensure that it exists in PORTDIR's
1259 # license directory.
1260 for mypos in range(0,len(myuse)):
1261 # Need to check for "||" manually as no portage
1262 # function will remove it without removing values.
1263 if myuse[mypos] not in liclist and myuse[mypos] != "||":
1264 stats["LICENSE.invalid"]=stats["LICENSE.invalid"]+1
1265 fails["LICENSE.invalid"].append(x+"/"+y+".ebuild: %s" % myuse[mypos])
1268 myuse = myaux["KEYWORDS"].split()
1276 if myskey not in kwlist:
1277 stats["KEYWORDS.invalid"] += 1
1278 fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s" % mykey)
1279 elif not profiles.has_key(myskey):
1280 stats["KEYWORDS.invalid"] += 1
1281 fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s (profile invalid)" % mykey)
1284 myear = time.gmtime(os.stat(checkdir+"/"+y+".ebuild")[ST_MTIME])[0]
1285 gentoo_copyright = re.compile(r'^# Copyright ((1999|200\d)-)?' + str(myear) + r' Gentoo Foundation')
1286 gentoo_license = re.compile(r'^# Distributed under the terms of the GNU General Public License v2$')
1287 cvs_header = re.compile(r'^#\s*\$Header.*\$$')
1288 ignore_line = re.compile(r'(^$)|(^(\t)*#)')
1289 leading_spaces = re.compile(r'^[\S\t]')
1290 trailing_whitespace = re.compile(r'.*([\S]$)')
1291 readonly_assignment = re.compile(r'^\s*(export\s+)?(A|CATEGORY|P|PV|PN|PR|PVR|PF|D|WORKDIR|FILESDIR|FEATURES|USE)=')
1292 line_continuation = re.compile(r'([^#]*\S)(\s+|\t)\\$')
1294 previous_line = None
1295 for line in input(checkdir+"/"+y+".ebuild"):
1297 # Gentoo copyright check
1299 match = gentoo_copyright.match(line)
1301 myerrormsg = "Copyright header Error. Possibly date related."
1302 stats["ebuild.badheader"] +=1
1303 fails["ebuild.badheader"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1304 # Gentoo license check
1306 match = gentoo_license.match(line)
1308 myerrormsg = "Gentoo License Error."
1309 stats["ebuild.badheader"] +=1
1310 fails["ebuild.badheader"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1313 match = cvs_header.match(line)
1315 myerrormsg = "CVS Header Error."
1316 stats["ebuild.badheader"] +=1
1317 fails["ebuild.badheader"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1319 match = ignore_line.match(line)
1321 # Excluded Blank lines and full line comments. Good!
1322 # Leading Spaces Check
1323 match = leading_spaces.match(line)
1325 #Line has got leading spaces. Bad!
1326 myerrormsg = "Leading Space Syntax Error. Line %d" % linenum
1327 stats["ebuild.minorsyn"] +=1
1328 fails["ebuild.minorsyn"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1329 # Trailing whitespace check
1330 match = trailing_whitespace.match(line)
1332 #Line has got trailing whitespace. Bad!
1333 myerrormsg = "Trailing whitespace Syntax Error. Line %d" % linenum
1334 stats["ebuild.minorsyn"] +=1
1335 fails["ebuild.minorsyn"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1336 # Readonly variable assignment check
1337 match = readonly_assignment.match(line)
1338 # The regex can give a false positive for continued lines,
1339 # so we check the previous line to see if it was continued.
1340 if match and (not previous_line or not line_continuation.match(previous_line)):
1341 # invalid assignment, very bad!
1342 myerrormsg = "Readonly variable assignment to %s on line %d" % (match.group(2), linenum)
1343 stats["variable.readonly"] += 1
1344 fails["variable.readonly"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1345 previous_line = line
1348 # Check for 'all unstable' or 'all masked' -- ACCEPT_KEYWORDS is stripped
1349 # XXX -- Needs to be implemented in dep code. Can't determine ~arch nicely.
1350 #if not portage.portdb.xmatch("bestmatch-visible",x):
1351 # stats["ebuild.nostable"]+=1
1352 # fails["ebuild.nostable"].append(x)
1353 if allmasked and repolevel == 3:
1354 stats["ebuild.allmasked"]+=1
1355 fails["ebuild.allmasked"].append(x)
1357 #Pickle and save results for instant reuse in last and lfull
1358 if os.access(portage_const.CACHE_PATH, os.W_OK):
1359 for myobj, fname in (stats, "repo.stats"), (fails, "repo.fails"):
1360 fpath = os.path.join(portage_const.CACHE_PATH, fname)
1361 savef = open(fpath, 'w')
1362 pickle.dump(myobj, savef)
1364 portage.apply_secpass_permissions(fpath, gid=portage.portage_gid,
1368 #dofail will be set to 1 if we have failed in at least one non-warning category
1370 #dowarn will be set to 1 if we tripped any warnings
1372 #dofull will be set if we should print a "repoman full" informational message
1375 if not isCvs and x.find("notadded") != -1:
1379 if x not in qawarnings:
1383 print " "+x.ljust(30),
1385 print green(`stats[x]`)
1387 elif x in qawarnings:
1388 print yellow(`stats[x]`)
1390 print red(`stats[x]`)
1401 def grouplist(mylist,seperator="/"):
1402 """(list,seperator="/") -- Takes a list of elements; groups them into
1403 same initial element categories. Returns a dict of {base:[sublist]}
1404 From: ["blah/foo","spork/spatula","blah/weee/splat"]
1405 To: {"blah":["foo","weee/splat"], "spork":["spatula"]}"""
1408 xs=x.split(seperator)
1411 if xs[0] not in mygroups.keys():
1412 mygroups[xs[0]]=[seperator.join(xs[1:])]
1414 mygroups[xs[0]]+=[seperator.join(xs[1:])]
1417 if mymode!="commit":
1419 print bold("Note: type \"repoman full\" for a complete listing.")
1422 if dowarn and not dofail:
1424 print green("RepoMan sez:"),"\"You're only giving me a partial QA payment?\n I'll take it this time, but I'm not happy.\""
1426 print green("RepoMan sez:"),"\"OK for now, but I'll be back ...\""
1428 print green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\""
1433 print turquoise("Please fix these important QA issues first.")
1434 print green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n"
1437 if "--pretend" in myoptions:
1438 print green("RepoMan sez:"), "\"So, you want to play it safe. Good call.\"\n"
1440 if fails["digest.missing"]:
1441 print green("Creating missing digests...")
1442 for x in fails["digest.missing"]:
1445 myeb="/".join(xs[:-1])+"/"+xs[-1][7:]
1446 if "--pretend" in myoptions:
1447 print "(ebuild "+portdir+"/"+myeb+".ebuild digest)"
1449 retval=os.system("ebuild "+portdir+"/"+myeb+".ebuild digest")
1451 print "!!! Exiting on ebuild digest (shell) error code:",retval
1454 mycvstree=cvstree.getentries("./",recursive=1)
1455 if isCvs and not mycvstree:
1456 print "!!! It seems we don't have a cvs tree?"
1459 myunadded=cvstree.findunadded(mycvstree,recursive=1,basedir="./")
1462 for x in range(len(myunadded)-1,-1,-1):
1463 xs=myunadded[x].split("/")
1465 print "!!! files dir is not added! Please correct this."
1467 elif xs[-1]=="Manifest":
1468 # It's a manifest... auto add
1469 myautoadd+=[myunadded[x]]
1471 elif len(xs[-1])>=7:
1472 if xs[-1][:7]=="digest-":
1474 myeb="/".join(xs[:-1]+[xs[-1][7:]])+".ebuild"
1475 if os.path.exists(myeb):
1476 # Ebuild exists for digest... So autoadd it.
1477 myautoadd+=[myunadded[x]]
1481 print ">>> Auto-Adding missing digests..."
1482 if "--pretend" in myoptions:
1483 print "(/usr/bin/cvs add "+" ".join(myautoadd)+")"
1486 retval=os.system("/usr/bin/cvs add "+" ".join(myautoadd))
1488 print "!!! Exiting on cvs (shell) error code:",retval
1492 print red("!!! The following files are in your cvs tree but are not added to the master")
1493 print red("!!! tree. Please remove them from the cvs tree or add them to the master tree.")
1502 print "Performing a "+green("cvs -n up")+" with a little magic grep to check for updates."
1503 retval=getstatusoutput("/usr/bin/cvs -n up 2>&1 | egrep '^[^\?] .*' | egrep -v '^. .*/digest-[^/]+|^cvs server: .* -- ignored$'")
1505 mylines=retval[1].split("\n")
1510 if x[0] not in "UPMAR": # Updates,Patches,Modified,Added,Removed
1511 print red("!!! Please fix the following issues reported from cvs: ")+green("(U,P,M,A,R are ok)")
1512 print red("!!! Note: This is a pretend/no-modify pass...")
1516 elif x[0] in ["U","P"]:
1520 print green("Fetching trivial updates...")
1521 if "--pretend" in myoptions:
1522 print "(/usr/bin/cvs up "+" ".join(myupdates)+")"
1525 retval=os.system("/usr/bin/cvs up "+" ".join(myupdates))
1527 print "!!! cvs exited with an error. Terminating."
1531 mycvstree=cvstree.getentries("./",recursive=1)
1532 mychanged=cvstree.findchanged(mycvstree,recursive=1,basedir="./")
1533 mynew=cvstree.findnew(mycvstree,recursive=1,basedir="./")
1534 myremoved=cvstree.findremoved(mycvstree,recursive=1,basedir="./")
1535 if not (mychanged or mynew or myremoved):
1537 print green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\"\n"
1539 print "(Didn't find any changed files...)"
1543 # Manifests need to be regenerated after all other commits, so don't commit
1544 # them now even if they have changed.
1545 mychanged = [f for f in mychanged if "Manifest" != os.path.basename(f)]
1546 myupdates=mychanged+mynew
1549 headerstring="'\$(Header|Id)"
1550 headerstring+=".*\$'"
1551 for myfile in myupdates:
1552 myout=getstatusoutput("egrep -q "+headerstring+" "+myfile)
1554 myheaders.append(myfile)
1556 print "*",green(str(len(myupdates))),"files being committed...",green(str(len(myheaders))),"have headers that will change."
1557 print "*","Files with headers will cause the manifests to be made and recommited."
1559 print "myupdates:",myupdates
1560 print "myheaders:",myheaders
1563 if commitmessagefile:
1565 f = open(commitmessagefile)
1566 commitmessage = f.read()
1569 except (IOError, OSError), e:
1570 if e.errno == errno.ENOENT:
1571 portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % commitmessagefile)
1574 # We've read the content so the file is no longer needed.
1575 commitmessagefile = None
1576 if not commitmessage:
1577 print "Please enter a CVS commit message at the prompt:"
1578 while not commitmessage:
1580 commitmessage=raw_input(green("> "))
1581 except KeyboardInterrupt:
1584 commitmessage+="\n(Portage version: "+str(portage.VERSION)+")"
1585 except AttributeError:
1586 print "Failed to insert portage version in message!"
1587 commitmessage+="\n(Portage version: Unknown)"
1588 if myupdates or myremoved:
1589 myfiles = myupdates + myremoved
1590 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
1591 mymsg = os.fdopen(fd, "w")
1592 mymsg.write(commitmessage)
1596 print green("Using commit message:")
1597 print green("------------------------------------------------------------------------------")
1599 print green("------------------------------------------------------------------------------")
1603 if "--pretend" in myoptions:
1604 print "(/usr/bin/cvs -q commit -F %s %s)" % \
1605 (commitmessagefile, " ".join(myfiles))
1607 retval = spawn(["/usr/bin/cvs", "-q", "commit",
1608 "-F", commitmessagefile] + myfiles,
1611 os.unlink(commitmessagefile)
1615 print "!!! Exiting on cvs (shell) error code:",retval
1618 # Setup the GPG commands
1619 def gpgsign(filename):
1620 if "PORTAGE_GPG_KEY" not in repoman_settings:
1621 raise portage_exception.MissingParameter("PORTAGE_GPG_KEY is unset!")
1622 if "PORTAGE_GPG_DIR" not in repoman_settings:
1623 if os.environ.has_key("HOME"):
1624 repoman_settings["PORTAGE_GPG_DIR"] = os.path.join(os.environ["HOME"], ".gnupg")
1626 print "Automatically setting PORTAGE_GPG_DIR to",repoman_settings["PORTAGE_GPG_DIR"]
1628 raise portage_exception.MissingParameter("PORTAGE_GPG_DIR is unset!")
1629 gpg_dir = repoman_settings["PORTAGE_GPG_DIR"]
1630 if gpg_dir.startswith("~") and "HOME" in os.environ:
1631 repoman_settings["PORTAGE_GPG_DIR"] = os.path.join(
1632 os.environ["HOME"], gpg_dir[1:].lstrip(os.path.sep))
1633 if not os.access(repoman_settings["PORTAGE_GPG_DIR"], os.X_OK):
1634 raise portage_exception.InvalidLocation(
1635 "Unable to access directory: PORTAGE_GPG_DIR='%s'" % \
1636 repoman_settings["PORTAGE_GPG_DIR"])
1637 gpgcmd = "gpg --sign --clearsign --yes "
1638 gpgcmd+= "--default-key "+repoman_settings["PORTAGE_GPG_KEY"]
1639 if repoman_settings.has_key("PORTAGE_GPG_DIR"):
1640 gpgcmd += " --homedir "+repoman_settings["PORTAGE_GPG_DIR"]
1641 if "--pretend" in myoptions:
1642 print "("+gpgcmd+" "+filename+")"
1644 rValue = os.system(gpgcmd+" "+filename)
1645 if rValue == os.EX_OK:
1646 os.rename(filename+".asc", filename)
1648 raise portage_exception.PortageException("!!! gpg exited with '" + str(rValue) + "' status")
1650 manifest_commit_required = True
1651 if myheaders or myupdates or myremoved or mynew:
1652 myfiles=myheaders+myupdates+myremoved+mynew
1653 for x in range(len(myfiles)-1, -1, -1):
1654 if myfiles[x].count("/") < 4-repolevel:
1657 if repolevel==3: # In a package dir
1658 repoman_settings["O"]=os.getcwd()
1659 portage.digestgen([], repoman_settings, manifestonly=1,
1661 elif repolevel==2: # In a category dir
1668 mydone.append(xs[0])
1669 repoman_settings["O"]=os.path.join(os.getcwd(), xs[0])
1670 portage.digestgen([], repoman_settings, manifestonly=1,
1672 elif repolevel==1: # repo-cvsroot
1673 print green("RepoMan sez:"), "\"You're rather crazy... doing the entire repository.\"\n"
1678 if "/".join(xs[:2]) in mydone:
1680 mydone.append("/".join(xs[:2]))
1681 repoman_settings["O"]=os.path.join(os.getcwd(), x[0], x[1])
1682 portage.digestgen([], repoman_settings, manifestonly=1,
1685 print red("I'm confused... I don't know where I am!")
1688 # Force an unsigned commit when more than one Manifest needs to be signed.
1689 if repolevel < 3 and "sign" in repoman_settings.features:
1690 if "--pretend" in myoptions:
1691 print "(/usr/bin/cvs -q commit -F "+commitmessagefile+")"
1693 mymsg=open(commitmessagefile,"w")
1694 mymsg.write(commitmessage)
1695 mymsg.write("\n (Unsigned Manifest commit)")
1697 retval=os.system("/usr/bin/cvs -q commit -F "+commitmessagefile)
1699 print "!!! Exiting on cvs (shell) error code:",retval
1701 manifest_commit_required = False
1704 if "sign" in repoman_settings.features:
1707 if repolevel==3: # In a package dir
1708 repoman_settings["O"] = "."
1709 gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
1710 elif repolevel==2: # In a category dir
1718 mydone.append(xs[0])
1719 repoman_settings["O"] = os.path.join(".", xs[0])
1720 gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
1721 elif repolevel==1: # repo-cvsroot
1722 print green("RepoMan sez:"), "\"You're rather crazy... doing the entire repository.\"\n"
1728 if "/".join(xs[:2]) in mydone:
1730 mydone.append("/".join(xs[:2]))
1731 repoman_settings["O"] = os.path.join(".", xs[0], xs[1])
1732 gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
1733 except portage_exception.PortageException, e:
1734 portage.writemsg("!!! %s\n" % str(e))
1735 portage.writemsg("!!! Disabled FEATURES='sign'\n")
1738 if manifest_commit_required or signed:
1739 if "--pretend" in myoptions:
1740 print "(/usr/bin/cvs -q commit -F "+commitmessagefile+")"
1742 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
1743 mymsg = os.fdopen(fd, "w")
1744 mymsg.write(commitmessage)
1746 mymsg.write("\n (Signed Manifest commit)")
1748 mymsg.write("\n (Unsigned Manifest commit)")
1750 retval=os.system("/usr/bin/cvs -q commit -F "+commitmessagefile)
1752 os.unlink(commitmessagefile)
1756 print "!!! Exiting on cvs (shell) error code:",retval
1761 print "CVS commit complete."
1763 print "repoman was too scared by not seeing any familiar cvs file that he forgot to commit anything"
1764 print green("RepoMan sez:"), "\"If everyone were like you, I'd be out of business!\"\n"