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 from os import path as osp
30 sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
32 del os.environ["PORTAGE_LEGACY_GLOBALS"]
34 import portage_checksum
37 portage_dep._dep_check_strict = True
38 import portage_exception
43 from portage_manifest import Manifest
44 from portage_exception import ParseError
45 from portage_exec import spawn
47 from output import bold, darkgreen, darkred, green, nocolor, red, turquoise, yellow
49 from commands import getstatusoutput
50 from fileinput import input
51 from grp import getgrnam
52 from stat import S_ISDIR, ST_CTIME, ST_GID, ST_MTIME
54 # A sane umask is needed for files that portage creates.
56 repoman_settings = portage.config(local_config=False,
57 config_incrementals=portage_const.INCREMENTALS)
58 repoman_settings.lock()
60 if repoman_settings.get("NOCOLOR", "").lower() in ("yes", "true") or \
61 not sys.stdout.isatty():
65 print exename+": "+txt
71 help(exitstatus=-1,helpfulness=0)
75 def exithandler(signum=None,frame=None):
76 sys.stderr.write("\n"+exename+": Interrupted; exiting...\n")
78 os.kill(0,signal.SIGKILL)
79 signal.signal(signal.SIGINT,exithandler)
81 shortmodes={"ci":"commit"}
83 "scan" : "Scan directory tree for QA issues (default)",
84 "fix" : "Fix simple QA issues (stray digests, missing digests)",
85 "full" : "Scan directory tree for QA issues (full listing)",
86 "help" : "Show this screen",
87 "commit" : "Scan directory tree for QA issues; if OK, commit via cvs",
88 "last" : "Remember report from last run",
89 "lfull" : "Remember report from last run (full listing)"
91 modes=modeshelp.keys()
93 "--commitmsg" : "Adds a commit message via the command line",
94 "--commitmsgfile" : "Adds a commit message from the specified file",
95 "--help" : "Show this screen",
96 "--ignore-arches" : "Ignore arch-specific failures (where arch != host)",
97 "--ignore-masked" : "Ignore masked packages (not allowed with commit mode)",
98 "--pretend" : "Don't commit or fix anything; just show what would be done",
99 "--quiet" : "Be less verbose about extraneous info",
100 "--verbose" : "Displays every package name while checking",
101 "--version" : "Show version info",
102 "--xmlparse" : "Forces the metadata.xml parse check to be carried out"
104 repoman_shortoptions={
106 "-i" : "--ignore-masked",
107 "-I" : "--ignore-arches",
108 "-m" : "--commitmsg",
109 "-M" : "--commitmsgfile",
116 repoman_shortoptions_rev=dict([(v,k) for (k,v) in repoman_shortoptions.items()])
117 options=repoman_options.keys()
120 "CVS/Entries.IO_error":"Attempting to commit, and an IO error was encountered access the Entries file",
121 "digest.partial":"Digest files do not contain all corresponding URI elements",
122 "digest.assumed":"Existing digest must be assumed correct (Package level only)",
123 "digestentry.unused":"Digest/Manifest entry has no matching SRC_URI entry",
124 "digest.fail":"Digest does not match the specified local file",
125 "digest.stray":"Digest files that do not have a corresponding ebuild",
126 "digest.missing":"Digest files that are missing (ebuild exists, digest doesn't)",
127 "digest.disjointed":"Digests not added to cvs when the matching ebuild has been added",
128 "digest.notadded":"Digests that exist but have not been added to cvs",
129 "digest.unmatch":"Digests which are incomplete (please check if your USE/ARCH includes all files)",
130 "ebuild.invalidname":"Ebuild files with a non-parseable or syntactically incorrect name (or using 2.1 versioning extensions)",
131 "ebuild.namenomatch":"Ebuild files that do not have the same name as their parent directory",
132 "changelog.missing":"Missing ChangeLog files",
133 "ebuild.disjointed":"Ebuilds not added to cvs when the matching digest has been added",
134 "ebuild.notadded":"Ebuilds that exist but have not been added to cvs",
135 "changelog.notadded":"ChangeLogs that exist but have not been added to cvs",
136 "filedir.missing":"Package lacks a files directory",
137 "file.executable":"Ebuilds, digests, metadata.xml, Manifest, and ChangeLog do note need the executable bit",
138 "file.size":"Files in the files directory must be under 20k",
139 "file.name":"File/dir name must be composed of only the following chars: %s " % allowed_filename_chars,
140 "file.UTF8":"File is not UTF8 compliant",
141 "KEYWORDS.missing":"Ebuilds that have a missing or empty KEYWORDS variable",
142 "KEYWORDS.stupid":"Ebuilds that use KEYWORDS=-* instead of package.mask",
143 "LICENSE.missing":"Ebuilds that have a missing or empty LICENSE variable",
144 "DESCRIPTION.missing":"Ebuilds that have a missing or empty DESCRIPTION variable",
145 "SLOT.missing":"Ebuilds that have a missing or empty SLOT variable",
146 "HOMEPAGE.missing":"Ebuilds that have a missing or empty HOMEPAGE variable",
147 "DEPEND.bad":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds)",
148 "RDEPEND.bad":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds)",
149 "PDEPEND.bad":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds)",
150 "DEPEND.badmasked":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds)",
151 "RDEPEND.badmasked":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds)",
152 "PDEPEND.badmasked":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds)",
153 "DEPEND.badindev":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds) in developing arch",
154 "RDEPEND.badindev":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds) in developing arch",
155 "PDEPEND.badindev":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds) in developing arch",
156 "DEPEND.badmaskedindev":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds) in developing arch",
157 "RDEPEND.badmaskedindev":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds) in developing arch",
158 "PDEPEND.badmaskedindev":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds) in developing arch",
159 "DEPEND.syntax":"Syntax error in DEPEND (usually an extra/missing space/parenthesis)",
160 "RDEPEND.syntax":"Syntax error in RDEPEND (usually an extra/missing space/parenthesis)",
161 "PDEPEND.syntax":"Syntax error in PDEPEND (usually an extra/missing space/parenthesis)",
162 "LICENSE.syntax":"Syntax error in LICENSE (usually an extra/missing space/parenthesis)",
163 "PROVIDE.syntax":"Syntax error in PROVIDE (usually an extra/missing space/parenthesis)",
164 "SRC_URI.syntax":"Syntax error in SRC_URI (usually an extra/missing space/parenthesis)",
165 "ebuild.syntax":"Error generating cache entry for ebuild; typically caused by ebuild syntax error",
166 "ebuild.output":"A simple sourcing of the ebuild produces output; this breaks ebuild policy.",
167 "ebuild.nesteddie":"Placing 'die' inside ( ) prints an error, but doesn't stop the ebuild.",
168 "variable.readonly":"Assigning a readonly variable",
169 "LIVEVCS.stable":"This ebuild is a live checkout from a VCS but has stable keywords.",
170 "IUSE.invalid":"This ebuild has a variable in IUSE that is not in the use.desc or use.local.desc file",
171 "LICENSE.invalid":"This ebuild is listing a license that doesnt exist in portages license/ dir.",
172 "KEYWORDS.invalid":"This ebuild contains KEYWORDS that are not listed in profiles/arch.list or for which no valid profile was found",
173 "ebuild.nostable":"There are no ebuilds that are marked as stable for your ARCH",
174 "ebuild.allmasked":"All ebuilds are masked for this package (Package level only)",
175 "ebuild.majorsyn":"This ebuild has a major syntax error that may cause the ebuild to fail partially or fully",
176 "ebuild.minorsyn":"This ebuild has a minor syntax error that contravenes gentoo coding style",
177 "ebuild.badheader":"This ebuild has a malformed header",
178 "metadata.missing":"Missing metadata.xml files",
179 "metadata.bad":"Bad metadata.xml files",
180 "virtual.versioned":"PROVIDE contains virtuals with versions",
181 "virtual.exists":"PROVIDE contains existing package names",
182 "virtual.unavailable":"PROVIDE contains a virtual which contains no profile default",
183 "usage.obsolete":"The ebuild makes use of an obsolete construct"
186 qacats = qahelp.keys()
191 "changelog.notadded",
200 "digestentry.unused",
201 "DEPEND.badmasked","RDEPEND.badmasked","PDEPEND.badmasked",
202 "DEPEND.badindev","RDEPEND.badindev","PDEPEND.badindev",
203 "DEPEND.badmaskedindev","RDEPEND.badmaskedindev","PDEPEND.badmaskedindev",
214 "virtual.unavailable",
219 missingvars=["KEYWORDS","LICENSE","DESCRIPTION","HOMEPAGE","SLOT"]
220 allvars=portage.auxdbkeys
222 commitmessagefile=None
223 for x in missingvars:
226 print "* missingvars values need to be added to qahelp ("+x+")"
235 print exename+" "+version
238 def help(exitstatus=1,helpfulness=1):
243 print green(exename+" "+version)
244 print " \"Quality is job zero.\""
245 print " Copyright 1999-2006 Gentoo Foundation"
246 print " Distributed under the terms of the GNU General Public License v2"
248 print bold(" Usage:"),turquoise(exename),"[",green("options"),"] [",green("mode"),"]"
250 print bold(" Modes:"),turquoise("scan (default)"),
252 print "|",turquoise(x),
255 print " "+green("Options".ljust(20)+" Description")
257 if repoman_shortoptions_rev.has_key(x):
258 shopt=repoman_shortoptions_rev[x]+", "+x
261 print " "+shopt.ljust(20),repoman_options[x]
263 print " "+green("Modes".ljust(20)+" Description")
265 print " "+x.ljust(20),modeshelp[x]
268 print " "+green("QA keyword".ljust(20)+" Description")
270 print " "+x.ljust(20),qahelp[x]
272 if (exitstatus != -1):
279 #Retrieve and unpickle stats and fails from saved files
280 savedf=open('/var/cache/edb/repo.stats','r')
281 stats = pickle.load(savedf)
283 savedf=open('/var/cache/edb/repo.fails','r')
284 fails = pickle.load(savedf)
286 except SystemExit, e:
287 raise # Need to propogate this
289 err("Error retrieving last repoman run data; exiting.")
291 #dofail will be set to 1 if we have failed in at least one non-warning category
293 #dowarn will be set to 1 if we tripped any warnings
295 #dofull will be set if we should print a "repoman full" informational message
299 print green("RepoMan remembers...")
304 if x not in qawarnings:
308 print " "+ x.ljust(20),
310 print green(`stats[x]`)
312 elif x in qawarnings:
313 print yellow(`stats[x]`)
315 print red(`stats[x]`)
327 print bold("Note: type \"repoman lfull\" for a complete listing of repomans last run.")
329 if dowarn and not dofail:
330 print green("RepoMan sez:"),"\"You only gave me a partial QA payment last time?\n I took it, but I wasn't happy.\""
332 print green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\""
340 while x < len(sys.argv):
341 if sys.argv[x] in shortmodes.keys():
342 sys.argv[x]=shortmodes[sys.argv[x]]
343 elif sys.argv[x] in repoman_shortoptions.keys():
344 sys.argv[x] = repoman_shortoptions[sys.argv[x]]
345 if sys.argv[x] in modes:
349 err("Please specify either \""+mymode+"\" or \""+sys.argv[x]+"\", but not both.")
350 elif sys.argv[x] in options:
352 if (optionx=="--commitmsg") and (len(sys.argv)>=(x+1)):
353 commitmessage=sys.argv[x+1]
355 elif (optionx=="--commitmsgfile") and (len(sys.argv)>=(x+1)):
356 commitmessagefile=sys.argv[x+1]
358 elif (optionx=="--verbose"):
360 elif (optionx=="--quiet"):
362 elif optionx not in myoptions:
363 myoptions.append(optionx)
365 err_help("\""+sys.argv[x]+"\" is not a valid mode or option.")
369 if mymode=="help" or ("--help" in myoptions):
371 if ("--version" in myoptions):
373 if mymode=="last" or (mymode=="lfull"):
375 if mymode == "commit":
376 while "--ignore-masked" in myoptions:
377 myoptions.remove("--ignore-masked")
379 from portage import normalize_path
382 if os.path.isdir("CVS"):
385 if mymode == "commit" and \
387 "--pretend" not in myoptions:
389 print darkgreen("Not in a CVS repository; enabling pretend mode.")
390 myoptions.append("--pretend");
393 def have_profile_dir(path, maxdepth=3):
394 while path != "/" and maxdepth:
395 if os.path.exists(path + "/profiles/package.mask"):
396 return normalize_path(path)
397 path = normalize_path(path + "/..")
403 if "PWD" in os.environ and os.environ["PWD"] != mydir and \
404 os.path.realpath(os.environ["PWD"]) == mydir:
405 # getcwd() returns the canonical path but that makes it hard for repoman to
406 # orient itself if the user has symlinks in their portage tree structure.
407 # We use os.environ["PWD"], if available, to get the non-canonical path of
408 # the current working directory (from the shell).
409 mydir = os.environ["PWD"]
410 mydir = normalize_path(mydir)
414 for overlay in repoman_settings["PORTDIR_OVERLAY"].split():
415 if overlay[-1] != "/":
417 if mydir.startswith(overlay):
418 portdir_overlay = overlay
419 subdir = mydir[len(overlay):]
420 if subdir and subdir[-1] != "/":
422 if have_profile_dir(mydir, subdir.count("/")):
423 portdir = portdir_overlay
426 if not portdir_overlay:
427 if (repoman_settings["PORTDIR"] + os.path.sep).startswith(mydir):
428 portdir_overlay = repoman_settings["PORTDIR"]
430 portdir_overlay = have_profile_dir(mydir)
431 portdir = portdir_overlay
433 if not portdir_overlay:
434 sys.stderr.write("Repoman is unable to determine PORTDIR or PORTDIR_OVERLAY from" + \
435 " the current\nworking directory.\n")
439 portdir = repoman_settings["PORTDIR"]
441 portdir = normalize_path(portdir)
442 portdir_overlay = normalize_path(portdir_overlay)
444 os.environ["PORTDIR"] = portdir
445 if portdir_overlay != portdir:
446 os.environ["PORTDIR_OVERLAY"] = portdir_overlay
448 os.environ["PORTDIR_OVERLAY"] = ""
451 print "\nSetting paths:"
452 print "PORTDIR = \""+os.environ["PORTDIR"]+"\""
453 print "PORTDIR_OVERLAY = \""+os.environ["PORTDIR_OVERLAY"]+"\""
455 # Now that PORTDIR_OVERLAY is properly overridden, create the portdb.
456 repoman_settings = portage.config(local_config=False,
457 config_incrementals=portage_const.INCREMENTALS)
458 trees = portage.create_trees()
459 trees["/"]["porttree"].settings = repoman_settings
460 portdb = trees["/"]["porttree"].dbapi
461 portdb.mysettings = repoman_settings
462 # dep_zapdeps looks at the vardbapi, but it shouldn't for repoman.
463 del trees["/"]["vartree"]
466 myreporoot = os.path.basename(portdir_overlay)
467 myreporoot += mydir[len(portdir_overlay):-1]
469 reposplit=myreporoot.split("/")
470 repolevel=len(reposplit)
472 # check if it's in $PORTDIR/$CATEGORY/$PN , otherwise bail if commiting.
473 # Reason for this is if they're trying to commit in just $FILESDIR/*, the Manifest needs updating.
474 # this check ensure that repoman knows where it is, and the manifest recommit is at least possible.
475 if mymode == "commit" and repolevel not in [1,2,3]:
476 print red("***")+" Commit attempts *must* be from within a cvs co, category, or package directory."
477 print red("***")+" Attempting to commit from a packages files directory will be blocked for instance."
478 print red("***")+" This is intended behaviour, to ensure the manifest is recommited for a package."
480 err("Unable to identify level we're commiting from for %s" % '/'.join(reposplit))
482 startdir = normalize_path(mydir)
484 for x in range(0,repolevel-1):
485 repodir = os.path.dirname(repodir)
488 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.")
489 if "--pretend" in myoptions:
490 print green("\nRepoMan does a once-over of the neighborhood...")
492 print green("\nRepoMan scours the neighborhood...")
494 def parse_use_local_desc(mylines, usedict=None):
495 """returns a dict of the form {cpv:set(flags)}"""
501 if not l or l.startswith("#"):
503 mysplit = l.split(None, 1)
506 mysplit = mysplit[0].split(":")
507 if len(mysplit) != 2:
508 raise ParseError("line %d: Malformed input: '%s'" % \
509 (lineno, l.rstrip("\n")))
510 usedict.setdefault(mysplit[0], set())
511 usedict[mysplit[0]].add(mysplit[1])
514 # retreive local USE list
517 f = open(os.path.join(portdir, "profiles", "use.local.desc"))
518 parse_use_local_desc(f, luselist)
520 except (IOError, OSError, ParseError), e:
521 print >> sys.stderr, str(e)
522 err("Couldn't read from use.local.desc")
524 if portdir_overlay != portdir:
525 filename = os.path.join(portdir_overlay, "profiles", "use.local.desc")
526 if os.path.exists(filename):
529 parse_use_local_desc(f, luselist)
531 except (IOError, OSError, ParseError), e:
532 print >> sys.stderr, str(e)
533 err("Couldn't read from '%s'" % filename)
536 # setup a uselist from portage
539 uselist=portage.grabfile(portdir+"/profiles/use.desc")
540 for l in range(0,len(uselist)):
541 uselist[l]=uselist[l].split()[0]
542 for var in repoman_settings["USE_EXPAND"].split():
543 vardescs = portage.grabfile(portdir+"/profiles/desc/"+var.lower()+".desc")
544 for l in range(0, len(vardescs)):
545 uselist.append(var.lower() + "_" + vardescs[l].split()[0])
546 except SystemExit, e:
547 raise # Need to propogate this
549 err("Couldn't read USE flags from use.desc")
551 # retrieve a list of current licenses in portage
552 liclist = set(portage.listdir(os.path.join(portdir, "licenses")))
554 err("Couldn't find licenses?")
555 if portdir_overlay != portdir:
556 liclist.update(portage.listdir(os.path.join(portdir_overlay, "licenses")))
558 # retrieve list of offical keywords
559 kwlist = set(portage.grabfile(os.path.join(portdir, "profiles", "arch.list")))
561 err("Couldn't read KEYWORDS from arch.list")
563 manifest1_compat = not os.path.exists(
564 os.path.join(portdir, "manifest1_obsolete"))
565 if portdir_overlay != portdir:
566 kwlist.update(portage.grabfile(
567 os.path.join(portdir_overlay, "profiles", "arch.list")))
568 manifest1_compat = not os.path.exists(
569 os.path.join(portdir_overlay, "manifest1_obsolete"))
573 #we are inside a category directory
575 if catdir not in repoman_settings.categories:
577 mydirlist=os.listdir(startdir)
579 if x == "CVS" or x.startswith("."):
581 if os.path.isdir(startdir+"/"+x):
582 scanlist.append(catdir+"/"+x)
584 for x in repoman_settings.categories:
585 if not os.path.isdir(startdir+"/"+x):
587 for y in os.listdir(startdir+"/"+x):
588 if y == "CVS" or y.startswith("."):
590 if os.path.isdir(startdir+"/"+x+"/"+y):
591 scanlist.append(x+"/"+y)
593 catdir = reposplit[-2]
594 if catdir not in repoman_settings.categories:
596 scanlist.append(catdir+"/"+reposplit[-1])
599 descfile=portdir+"/profiles/profiles.desc"
600 if os.path.exists(descfile):
601 for x in portage.grabfile(descfile):
606 print "wrong format: \""+red(x)+"\" in "+descfile
608 if not os.path.isdir(portdir+"/profiles/"+arch[1]):
609 print "Invalid "+arch[2]+" profile ("+arch[1]+") for arch "+arch[0]
611 if profiles.has_key(arch[0]):
612 profiles[arch[0]]+= [[arch[1], arch[2]]]
614 profiles[arch[0]] = [[arch[1], arch[2]]]
616 for x in repoman_settings.archlist():
619 if not profiles.has_key(x):
620 print red("\""+x+"\" doesn't have a valid profile listed in profiles.desc.")
621 print red("You need to either \"cvs update\" your profiles dir or follow this")
622 print red("up with the "+x+" team.")
625 print red("profiles.desc does not exist: "+descfile)
626 print red("You need to do \"cvs update\" in profiles dir.")
637 xmllint_capable = False
638 if getstatusoutput('which xmllint')[0] != 0:
639 print red("!!! xmllint not found. Can't check metadata.xml.\n")
640 if "--xmlparse" in myoptions or repolevel==3:
641 print red("!!!")+" sorry, xmllint is needed. failing\n"
644 #hardcoded paths/urls suck. :-/
648 # if it's been over a week since fetching (or the system clock is fscked), grab an updated copy of metadata.dtd
649 # clock is fscked or it's been a week. time to grab a new one.
650 ct=os.stat(portage.CACHE_PATH + '/metadata.dtd')[ST_CTIME]
651 if abs(time.time() - ct) > (60*60*24*7):
652 # don't trap the exception, we're watching for errno 2 (file not found), anything else is a bug.
657 except (OSError,IOError), e:
659 print red("!!!")+" caught exception '%s' for %s/metadata.dtd, bailing" % (str(e), portage.CACHE_PATH)
664 print green("***")+" the local copy of metadata.dtd needs to be refetched, doing that now"
667 if os.path.exists(repoman_settings["DISTDIR"]+'/metadata.dtd'):
668 os.remove(repoman_settings["DISTDIR"]+'/metadata.dtd')
669 val=portage.fetch(['http://www.gentoo.org/dtd/metadata.dtd'],repoman_settings,fetchonly=0, \
673 os.remove(portage.CACHE_PATH+'/metadata.dtd')
674 shutil.copy(repoman_settings["DISTDIR"]+'/metadata.dtd',portage.CACHE_PATH+'/metadata.dtd')
675 os.chown(portage.CACHE_PATH+'/metadata.dtd',os.getuid(),portage.portage_gid)
676 os.chmod(portage.CACHE_PATH+'/metadata.dtd',0664)
679 except SystemExit, e:
680 raise # Need to propogate this
683 print red("!!!")+" attempting to fetch 'http://www.gentoo.org/dtd/metadata.dtd', caught"
684 print red("!!!")+" exception '%s' though." % str(e)
687 print red("!!!")+" fetching new metadata.dtd failed, aborting"
689 #this can be problematic if xmllint changes their output
693 def x11_deprecation_check(depstr):
694 if depstr.find("virtual/x11") == -1:
696 depsplit = depstr.split()
699 for token in depsplit:
709 if token.find("virtual/x11") != -1 and (not ok_stack or not ok_stack[-1]):
715 arch_xmatch_caches = {}
717 #ebuilds and digests added to cvs respectively.
719 print "checking package " + x
722 catdir,pkgdir=x.split("/")
723 checkdir=repodir+"/"+x
724 checkdirlist=os.listdir(checkdir)
726 for y in checkdirlist:
727 if y[-7:]==".ebuild":
728 ebuildlist.append(y[:-7])
729 if y in ["Manifest","ChangeLog","metadata.xml"]:
730 if os.stat(checkdir+"/"+y)[0] & 0x0248:
731 stats["file.executable"] += 1
732 fails["file.executable"].append(checkdir+"/"+y)
735 for y in checkdirlist:
736 for c in y.strip(os.path.sep):
737 if c not in allowed_filename_chars_set:
738 stats["file.name"] += 1
739 fails["file.name"].append("%s/%s: char '%s'" % (checkdir, y, c))
742 if not (y in ("ChangeLog", "metadata.xml") or y.endswith(".ebuild")):
746 for l in codecs.open(checkdir+"/"+y, "r", "utf8"):
748 except UnicodeDecodeError, ue:
749 stats["file.UTF8"] += 1
750 s = ue.object[:ue.start]
754 s = s[s.rfind("\n") + 1:]
755 fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s))
759 mystat=os.stat(checkdir+"/files")[0]
760 if len(ebuildlist) and not S_ISDIR(mystat):
762 except SystemExit, e:
763 raise # Need to propogate this
765 stats["filedir.missing"] += 1
766 fails["filedir.missing"].append(checkdir)
769 myf=open(checkdir+"/CVS/Entries","r")
774 splitl=l[1:].split("/")
777 if splitl[0][-7:]==".ebuild":
778 eadded.append(splitl[0][:-7])
781 stats["CVS/Entries.IO_error"] += 1
782 fails["CVS/Entries.IO_error"].append(checkdir+"/CVS/Entries")
786 myf=open(checkdir+"/files/CVS/Entries","r")
791 splitl=l[1:].split("/")
794 if splitl[0][:7]=="digest-":
795 dadded.append(splitl[0][7:])
798 stats["CVS/Entries.IO_error"] += 1
799 fails["CVS/Entries.IO_error"].append(checkdir+"/files/CVS/Entries")
802 if mymode in ("fix", "commit") and \
803 "--pretend" not in myoptions:
804 repoman_settings["O"] = checkdir
805 if not portage.digestgen([], repoman_settings, myportdb=portdb):
806 print "Unable to generate manifest."
809 mf = Manifest(checkdir, repoman_settings["DISTDIR"])
810 mydigests=mf.getTypeDigests("DIST")
812 fetchlist_dict = portage.FetchlistDict(checkdir, repoman_settings, portdb)
814 for mykey in fetchlist_dict:
816 myfiles_all.extend(fetchlist_dict[mykey])
817 except portage_exception.InvalidDependString, e:
818 stats["SRC_URI.syntax"] = stats["SRC_URI.syntax"] + 1
819 fails["SRC_URI.syntax"].append(mykey+".ebuild SRC_URI: "+str(e))
821 myfiles_all = set(myfiles_all)
822 for entry in mydigests:
823 if entry not in myfiles_all:
824 stats["digestentry.unused"] += 1
825 fails["digestentry.unused"].append(checkdir+"::"+entry)
828 if os.path.exists(checkdir+"/files"):
829 filesdirlist=os.listdir(checkdir+"/files")
831 for y in filesdirlist:
832 if not y.startswith("digest-"):
834 if y[7:] not in dadded:
835 #digest not added to cvs
836 stats["digest.notadded"]=stats["digest.notadded"]+1
837 fails["digest.notadded"].append(x+"/files/"+y)
839 stats["digest.disjointed"]=stats["digest.disjointed"]+1
840 fails["digest.disjointed"].append(x+"/files/"+y)
842 if os.stat(checkdir+"/files/"+y)[0] & 0x0248:
843 stats["file.executable"] += 1
844 fails["file.executable"].append(x+"/files/"+y)
846 mykey = catdir + "/" + y[7:]
847 if y[7:] not in ebuildlist:
850 if "--pretend" in myoptions:
851 print "(cd "+repodir+"/"+x+"/files; cvs rm -f "+y+")"
853 os.system("(cd "+repodir+"/"+x+"/files; cvs rm -f "+y+")")
855 stats["digest.stray"]=stats["digest.stray"]+1
856 fails["digest.stray"].append(x+"/files/"+y)
860 myuris, myfiles = portdb.getfetchlist(mykey, all=True)
861 except portage_exception.InvalidDependString, e:
862 # Already handled above.
867 myubn = os.path.basename(myu)
868 if myubn not in uri_dict:
869 uri_dict[myubn] = [myu]
871 uri_dict[myubn] += [myu]
874 myff = repoman_settings["DISTDIR"] + "/" + myf
875 if not mydigests.has_key(myf):
876 uri_settings = portage.config(clone=repoman_settings)
878 if not portage.fetch(uri_dict[myf], uri_settings):
879 stats["digest.unmatch"] += 1
880 fails["digest.unmatch"].append(y+"::"+myf)
882 eb_name = portdb.findname2(mykey)[0]
883 portage.doebuild(eb_name, "digest", "/",
884 uri_settings, tree="porttree",
887 stats["digest.partial"] += 1
888 fails["digest.partial"].append(y+"::"+myf)
889 elif "assume-digests" not in repoman_settings.features:
890 if os.path.exists(myff):
891 if not portage_checksum.verify_all(myff, mydigests[myf]):
892 stats["digest.fail"] += 1
893 fails["digest.fail"].append(y+"::"+myf)
895 stats["digest.assumed"] += 1
896 fails["digest.assumed"].append(y+"::"+myf)
898 # recurse through files directory
899 # use filesdirlist as a stack, appending directories as needed so people can't hide > 20k files in a subdirectory.
901 y = filesdirlist.pop(0)
903 mystat = os.stat(checkdir+"/files/"+y)
906 # don't worry about it. it likely was removed via fix above.
910 if S_ISDIR(mystat.st_mode):
913 for z in os.listdir(checkdir+"/files/"+y):
916 filesdirlist.append(y+"/"+z)
917 # current policy is no files over 20k, this is the check.
918 elif mystat.st_size > 20480:
919 stats["file.size"] += 1
920 fails["file.size"].append("("+ str(mystat.st_size/1024) + "K) "+x+"/files/"+y)
922 for c in os.path.basename(y.rstrip(os.path.sep)):
923 if c not in allowed_filename_chars_set:
924 stats["file.name"] += 1
925 fails["file.name"].append("%s/files/%s: char '%s'" % (checkdir, y, c))
930 if "ChangeLog" not in checkdirlist:
931 stats["changelog.missing"]+=1
932 fails["changelog.missing"].append(x+"/ChangeLog")
934 #metadata.xml file check
935 if "metadata.xml" not in checkdirlist:
936 stats["metadata.missing"]+=1
937 fails["metadata.missing"].append(x+"/metadata.xml")
938 #metadata.xml parse check
940 #Only carry out if in package directory or check forced
942 st=getstatusoutput("xmllint --nonet --noout --dtdvalid %s/metadata.dtd %s/metadata.xml" % (portage.CACHE_PATH, checkdir))
944 for z in st[1].split("\n"):
946 stats["metadata.bad"]+=1
947 fails["metadata.bad"].append(x+"/metadata.xml")
952 if os.stat(checkdir+"/"+y+".ebuild")[0] & 0x0248:
953 stats["file.executable"] += 1
954 fails["file.executable"].append(x+"/"+y+".ebuild")
956 #ebuild not added to cvs
957 stats["ebuild.notadded"]=stats["ebuild.notadded"]+1
958 fails["ebuild.notadded"].append(x+"/"+y+".ebuild")
960 stats["ebuild.disjointed"]=stats["ebuild.disjointed"]+1
961 fails["ebuild.disjointed"].append(x+"/"+y+".ebuild")
962 if manifest1_compat and \
963 not os.path.exists(os.path.join(checkdir, "files", "digest-"+y)):
965 if "--pretend" in myoptions:
966 print "You will need to run:"
967 print " /usr/bin/ebuild "+repodir+"/"+x+"/"+y+".ebuild digest"
969 retval=os.system("/usr/bin/ebuild "+repodir+"/"+x+"/"+y+".ebuild digest")
971 print "!!! Exiting on ebuild digest (shell) error code:",retval
974 stats["digest.missing"]=stats["digest.missing"]+1
975 fails["digest.missing"].append(x+"/files/digest-"+y)
976 myesplit=portage.pkgsplit(y)
977 if myesplit is None or myesplit[0] != x.split("/")[-1]:
978 stats["ebuild.invalidname"]=stats["ebuild.invalidname"]+1
979 fails["ebuild.invalidname"].append(x+"/"+y+".ebuild")
981 elif myesplit[0]!=pkgdir:
982 print pkgdir,myesplit[0]
983 stats["ebuild.namenomatch"]=stats["ebuild.namenomatch"]+1
984 fails["ebuild.namenomatch"].append(x+"/"+y+".ebuild")
988 zip(allvars, portdb.aux_get(os.path.join(catdir, y), allvars)))
990 stats["ebuild.syntax"]=stats["ebuild.syntax"]+1
991 fails["ebuild.syntax"].append(x+"/"+y+".ebuild")
994 stats["ebuild.output"]=stats["ebuild.output"]+1
995 fails["ebuild.output"].append(x+"/"+y+".ebuild")
998 # Test for negative logic and bad words in the RESTRICT var.
999 #for x in myaux[allvars.index("RESTRICT")].split():
1000 # if x.startswith("no"):
1001 # print "Bad RESTRICT value: %s" % x
1003 myaux["PROVIDE"] = portage_dep.use_reduce(
1004 portage_dep.paren_reduce(myaux["PROVIDE"]), matchall=1)
1005 except portage_exception.InvalidDependString, e:
1006 stats["PROVIDE.syntax"] = stats["PROVIDE.syntax"] + 1
1007 fails["PROVIDE.syntax"].append(mykey+".ebuild PROVIDE: "+str(e))
1010 myaux["PROVIDE"] = " ".join(portage.flatten(myaux["PROVIDE"]))
1011 for myprovide in myaux["PROVIDE"].split():
1012 prov_cp = portage.dep_getkey(myprovide)
1013 if prov_cp != myprovide:
1014 stats["virtual.versioned"]+=1
1015 fails["virtual.versioned"].append(x+"/"+y+".ebuild: "+myprovide)
1016 prov_pkg = portage.dep_getkey(
1017 portage.best(portdb.xmatch("match-all", prov_cp)))
1018 if prov_cp == prov_pkg:
1019 stats["virtual.exists"]+=1
1020 fails["virtual.exists"].append(x+"/"+y+".ebuild: "+prov_cp)
1022 for pos in range(0,len(missingvars)):
1023 if not myaux[missingvars[pos]]:
1024 myqakey=missingvars[pos]+".missing"
1025 stats[myqakey]=stats[myqakey]+1
1026 fails[myqakey].append(x+"/"+y+".ebuild")
1028 # KEYWORDS="-*" is a stupid replacement for package.mask and screws general KEYWORDS semantics
1029 if "-*" in myaux["KEYWORDS"].split():
1031 for kw in myaux["KEYWORDS"].split():
1037 stats["KEYWORDS.stupid"] += 1
1038 fails["KEYWORDS.stupid"].append(x+"/"+y+".ebuild")
1041 Ebuilds that inherit a "Live" eclasss (darcs,subversion,git,cvs,etc..) should
1042 not be allowed to be marked stable
1044 if set(["darcs","cvs","subversion","git"]).intersection(
1045 myaux["INHERITED"].split()):
1046 bad_stable_keywords = []
1047 for keyword in myaux["KEYWORDS"].split():
1048 if not keyword.startswith("~") and \
1049 not keyword.startswith("-"):
1050 bad_stable_keywords.append(keyword)
1052 if bad_stable_keywords:
1053 stats["LIVEVCS.stable"] += 1
1054 fails["LIVEVCS.stable"].append(
1055 x + "/" + y + ".ebuild with stable keywords:%s " % \
1056 bad_stable_keywords)
1057 del bad_stable_keywords
1059 if "--ignore-arches" in myoptions:
1060 arches = [[repoman_settings["ARCH"], repoman_settings["ARCH"],
1061 repoman_settings["ACCEPT_KEYWORDS"].split()]]
1064 for keyword in myaux["KEYWORDS"].split():
1065 if (keyword[0]=="-"):
1067 elif (keyword[0]=="~"):
1068 arches.append([keyword, keyword[1:], [keyword[1:], keyword]])
1070 arches.append([keyword, keyword, [keyword]])
1073 baddepsyntax = False
1074 badlicsyntax = False
1075 badprovsyntax = False
1076 catpkg = catdir+"/"+y
1077 myiuse = set(repoman_settings.archlist())
1078 for myflag in myaux["IUSE"].split():
1079 if myflag.startswith("+"):
1083 operator_tokens = set(["||", "(", ")"])
1084 type_list, badsyntax = [], []
1085 for mytype in ("DEPEND", "RDEPEND", "PDEPEND", "LICENSE", "PROVIDE"):
1086 mydepstr = myaux[mytype]
1088 if mydepstr.find(" ?") != -1:
1089 badsyntax.append("'?' preceded by space")
1092 # Missing closing parenthesis will result in a ValueError
1093 mydeplist = portage_dep.paren_reduce(mydepstr)
1094 # Missing opening parenthesis will result in a final "" element
1095 if "" in mydeplist or "(" in mydeplist:
1098 badsyntax.append("parenthesis mismatch")
1102 portage_dep.use_reduce(mydeplist, excludeall=myiuse)
1103 except portage_exception.InvalidDependString, e:
1104 badsyntax.append(str(e))
1106 for token in operator_tokens:
1107 if mydepstr.startswith(token+" "):
1108 myteststr = mydepstr[len(token):]
1110 myteststr = mydepstr
1111 if myteststr.endswith(" "+token):
1112 myteststr = myteststr[:-len(token)]
1113 while myteststr.find(" "+token+" ") != -1:
1114 myteststr = " ".join(myteststr.split(" "+token+" ", 1))
1115 if myteststr.find(token) != -1:
1116 badsyntax.append("'%s' not separated by space" % (token))
1119 if mytype in ("DEPEND", "RDEPEND", "PDEPEND"):
1120 for token in mydepstr.split():
1121 if token in operator_tokens or \
1122 token.endswith("?"):
1124 if not portage.isvalidatom(token, allow_blockers=True) or \
1125 ":" in token and myaux["EAPI"] == "0":
1126 badsyntax.append("'%s' not a valid atom" % token)
1128 type_list.extend([mytype] * (len(badsyntax) - len(type_list)))
1130 for m,b in zip(type_list, badsyntax):
1131 stats[m+".syntax"] += 1
1132 fails[m+".syntax"].append(catpkg+".ebuild "+m+": "+b)
1134 badlicsyntax = len(filter(lambda x:x=="LICENSE", type_list))
1135 badprovsyntax = len(filter(lambda x:x=="PROVIDE", type_list))
1136 baddepsyntax = len(type_list) != badlicsyntax + badprovsyntax
1137 badlicsyntax = badlicsyntax > 0
1138 badprovsyntax = badprovsyntax > 0
1140 if not baddepsyntax:
1141 if x11_deprecation_check(" ".join([myaux["DEPEND"], myaux["RDEPEND"], myaux["PDEPEND"]])):
1142 stats["usage.obsolete"] += 1
1143 fails["usage.obsolete"].append("%s/%s.ebuild: not migrated to modular X" % (x, y))
1145 for keyword,arch,groups in arches:
1147 if not profiles.has_key(arch):
1148 # A missing profile will create an error further down
1149 # during the KEYWORDS verification.
1152 for prof in profiles[arch]:
1154 profdir = portdir+"/profiles/"+prof[0]
1156 if prof[0] in arch_caches:
1157 dep_settings = arch_caches[prof[0]]
1159 dep_settings = portage.config(
1160 config_profile_path=profdir,
1161 config_incrementals=portage_const.INCREMENTALS,
1163 arch_caches[prof[0]] = dep_settings
1166 # Protect ACCEPT_KEYWORDS from config.regenerate()
1168 dep_settings.incrementals.remove("ACCEPT_KEYWORDS")
1172 xmatch_cache_key = (prof[0], tuple(groups))
1173 xcache = arch_xmatch_caches.get(xmatch_cache_key)
1177 xcache = portdb.xcache
1178 arch_xmatch_caches[xmatch_cache_key] = xcache
1180 trees["/"]["porttree"].settings = dep_settings
1181 portdb.mysettings = dep_settings
1182 portdb.xcache = xcache
1183 # for package.use.mask support inside dep_check
1184 dep_settings.setcpv("/".join((catdir, y)))
1185 dep_settings["ACCEPT_KEYWORDS"] = " ".join(groups)
1186 # just in case, prevent config.reset() from nuking these.
1187 dep_settings.backup_changes("ACCEPT_KEYWORDS")
1189 for myprovide in myaux["PROVIDE"].split():
1190 prov_cp = portage.dep_getkey(myprovide)
1191 if prov_cp not in dep_settings.getvirtuals():
1192 stats["virtual.unavailable"]+=1
1193 fails["virtual.unavailable"].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+prov_cp)
1195 if not baddepsyntax:
1196 ismasked = os.path.join(catdir, y) not in \
1197 portdb.xmatch("list-visible", x)
1199 if "--ignore-masked" in myoptions:
1201 #we are testing deps for a masked package; give it some lee-way
1203 matchmode="match-all"
1206 matchmode="match-visible"
1208 if prof[1] == "dev":
1209 suffix=suffix+"indev"
1211 for mytype,mypos in [["DEPEND",len(missingvars)],["RDEPEND",len(missingvars)+1],["PDEPEND",len(missingvars)+2]]:
1213 mykey=mytype+".bad"+suffix
1214 myvalue = myaux[mytype]
1218 mydep = portage.dep_check(myvalue, portdb,
1219 dep_settings, use="all", mode=matchmode,
1222 stats[mykey]=stats[mykey]+1
1223 fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(e[0]))
1228 #we have some unsolvable deps
1229 #remove ! deps, which always show up as unsatisfiable
1231 while d<len(mydep[1]):
1232 if mydep[1][d][0]=="!":
1236 #if we emptied out our list, continue:
1239 stats[mykey]=stats[mykey]+1
1240 fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(mydep[1]))
1242 stats[mykey]=stats[mykey]+1
1243 fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(mydep[1]))
1245 # this check needs work, it won't catch (\ndie)
1246 if not os.system("egrep '^[^#]*\([^)]*\<die\>' "+checkdir+"/"+y+".ebuild >/dev/null 2>&1"):
1247 stats["ebuild.nesteddie"]=stats["ebuild.nesteddie"]+1
1248 fails["ebuild.nesteddie"].append(x+"/"+y+".ebuild")
1249 # uselist checks - global
1252 for myflag in myaux["IUSE"].split():
1253 if myflag.startswith("+"):
1254 default_use.append(myflag)
1256 myuse.append(myflag)
1257 for mypos in range(len(myuse)-1,-1,-1):
1258 if myuse[mypos] and (myuse[mypos] in uselist):
1260 # uselist checks - local
1261 mykey = portage.dep_getkey(catpkg)
1262 if luselist.has_key(mykey):
1263 for mypos in range(len(myuse)-1,-1,-1):
1264 if myuse[mypos] and (myuse[mypos] in luselist[mykey]):
1266 if default_use and myaux["EAPI"] == "0":
1267 myuse += default_use
1268 for mypos in range(len(myuse)):
1269 stats["IUSE.invalid"]=stats["IUSE.invalid"]+1
1270 fails["IUSE.invalid"].append(x+"/"+y+".ebuild: %s" % myuse[mypos])
1273 if not badlicsyntax:
1274 myuse = myaux["LICENSE"]
1275 # Parse the LICENSE variable, remove USE conditions and
1277 myuse=portage_dep.use_reduce(portage_dep.paren_reduce(myuse), matchall=1)
1278 myuse=portage.flatten(myuse)
1279 # Check each entry to ensure that it exists in PORTDIR's
1280 # license directory.
1281 for mypos in range(0,len(myuse)):
1282 # Need to check for "||" manually as no portage
1283 # function will remove it without removing values.
1284 if myuse[mypos] not in liclist and myuse[mypos] != "||":
1285 stats["LICENSE.invalid"]=stats["LICENSE.invalid"]+1
1286 fails["LICENSE.invalid"].append(x+"/"+y+".ebuild: %s" % myuse[mypos])
1289 myuse = myaux["KEYWORDS"].split()
1297 if myskey not in kwlist:
1298 stats["KEYWORDS.invalid"] += 1
1299 fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s" % mykey)
1300 elif not profiles.has_key(myskey):
1301 stats["KEYWORDS.invalid"] += 1
1302 fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s (profile invalid)" % mykey)
1305 myear = time.gmtime(os.stat(checkdir+"/"+y+".ebuild")[ST_MTIME])[0]
1306 gentoo_copyright = re.compile(r'^# Copyright ((1999|200\d)-)?' + str(myear) + r' Gentoo Foundation')
1307 gentoo_license = re.compile(r'^# Distributed under the terms of the GNU General Public License v2$')
1308 cvs_header = re.compile(r'^#\s*\$Header.*\$$')
1309 ignore_line = re.compile(r'(^$)|(^(\t)*#)')
1310 leading_spaces = re.compile(r'^[\S\t]')
1311 trailing_whitespace = re.compile(r'.*([\S]$)')
1312 readonly_assignment = re.compile(r'^\s*(export\s+)?(A|CATEGORY|P|PV|PN|PR|PVR|PF|D|WORKDIR|FILESDIR|FEATURES|USE)=')
1313 line_continuation = re.compile(r'([^#]*\S)(\s+|\t)\\$')
1315 previous_line = None
1316 for line in input(checkdir+"/"+y+".ebuild"):
1318 # Gentoo copyright check
1320 match = gentoo_copyright.match(line)
1322 myerrormsg = "Copyright header Error. Possibly date related."
1323 stats["ebuild.badheader"] +=1
1324 fails["ebuild.badheader"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1325 # Gentoo license check
1327 match = gentoo_license.match(line)
1329 myerrormsg = "Gentoo License Error."
1330 stats["ebuild.badheader"] +=1
1331 fails["ebuild.badheader"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1334 match = cvs_header.match(line)
1336 myerrormsg = "CVS Header Error."
1337 stats["ebuild.badheader"] +=1
1338 fails["ebuild.badheader"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1340 match = ignore_line.match(line)
1342 # Excluded Blank lines and full line comments. Good!
1343 # Leading Spaces Check
1344 match = leading_spaces.match(line)
1346 #Line has got leading spaces. Bad!
1347 myerrormsg = "Leading Space Syntax Error. Line %d" % linenum
1348 stats["ebuild.minorsyn"] +=1
1349 fails["ebuild.minorsyn"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1350 # Trailing whitespace check
1351 match = trailing_whitespace.match(line)
1353 #Line has got trailing whitespace. Bad!
1354 myerrormsg = "Trailing whitespace Syntax Error. Line %d" % linenum
1355 stats["ebuild.minorsyn"] +=1
1356 fails["ebuild.minorsyn"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1357 # Readonly variable assignment check
1358 match = readonly_assignment.match(line)
1359 # The regex can give a false positive for continued lines,
1360 # so we check the previous line to see if it was continued.
1361 if match and (not previous_line or not line_continuation.match(previous_line)):
1362 # invalid assignment, very bad!
1363 myerrormsg = "Readonly variable assignment to %s on line %d" % (match.group(2), linenum)
1364 stats["variable.readonly"] += 1
1365 fails["variable.readonly"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1366 previous_line = line
1369 # Check for 'all unstable' or 'all masked' -- ACCEPT_KEYWORDS is stripped
1370 # XXX -- Needs to be implemented in dep code. Can't determine ~arch nicely.
1371 #if not portage.portdb.xmatch("bestmatch-visible",x):
1372 # stats["ebuild.nostable"]+=1
1373 # fails["ebuild.nostable"].append(x)
1374 if allmasked and repolevel == 3:
1375 stats["ebuild.allmasked"]+=1
1376 fails["ebuild.allmasked"].append(x)
1378 #Pickle and save results for instant reuse in last and lfull
1379 if os.access(portage_const.CACHE_PATH, os.W_OK):
1380 for myobj, fname in (stats, "repo.stats"), (fails, "repo.fails"):
1381 fpath = os.path.join(portage_const.CACHE_PATH, fname)
1382 savef = open(fpath, 'w')
1383 pickle.dump(myobj, savef)
1385 portage.apply_secpass_permissions(fpath, gid=portage.portage_gid,
1389 #dofail will be set to 1 if we have failed in at least one non-warning category
1391 #dowarn will be set to 1 if we tripped any warnings
1393 #dofull will be set if we should print a "repoman full" informational message
1396 if not isCvs and x.find("notadded") != -1:
1400 if x not in qawarnings:
1404 print " "+x.ljust(30),
1406 print green(`stats[x]`)
1408 elif x in qawarnings:
1409 print yellow(`stats[x]`)
1411 print red(`stats[x]`)
1422 def grouplist(mylist,seperator="/"):
1423 """(list,seperator="/") -- Takes a list of elements; groups them into
1424 same initial element categories. Returns a dict of {base:[sublist]}
1425 From: ["blah/foo","spork/spatula","blah/weee/splat"]
1426 To: {"blah":["foo","weee/splat"], "spork":["spatula"]}"""
1429 xs=x.split(seperator)
1432 if xs[0] not in mygroups.keys():
1433 mygroups[xs[0]]=[seperator.join(xs[1:])]
1435 mygroups[xs[0]]+=[seperator.join(xs[1:])]
1438 if mymode!="commit":
1440 print bold("Note: type \"repoman full\" for a complete listing.")
1443 if dowarn and not dofail:
1445 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.\""
1447 print green("RepoMan sez:"),"\"OK for now, but I'll be back ...\""
1449 print green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\""
1454 print turquoise("Please fix these important QA issues first.")
1455 print green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n"
1458 if "--pretend" in myoptions:
1459 print green("RepoMan sez:"), "\"So, you want to play it safe. Good call.\"\n"
1461 if fails["digest.missing"]:
1462 print green("Creating missing digests...")
1463 for x in fails["digest.missing"]:
1466 myeb="/".join(xs[:-1])+"/"+xs[-1][7:]
1467 if "--pretend" in myoptions:
1468 print "(ebuild "+portdir+"/"+myeb+".ebuild digest)"
1470 retval=os.system("ebuild "+portdir+"/"+myeb+".ebuild digest")
1472 print "!!! Exiting on ebuild digest (shell) error code:",retval
1475 mycvstree=cvstree.getentries("./",recursive=1)
1476 if isCvs and not mycvstree:
1477 print "!!! It seems we don't have a cvs tree?"
1480 myunadded=cvstree.findunadded(mycvstree,recursive=1,basedir="./")
1483 for x in range(len(myunadded)-1,-1,-1):
1484 xs=myunadded[x].split("/")
1486 print "!!! files dir is not added! Please correct this."
1488 elif xs[-1]=="Manifest":
1489 # It's a manifest... auto add
1490 myautoadd+=[myunadded[x]]
1492 elif len(xs[-1])>=7:
1493 if xs[-1][:7]=="digest-":
1495 myeb="/".join(xs[:-1]+[xs[-1][7:]])+".ebuild"
1496 if os.path.exists(myeb):
1497 # Ebuild exists for digest... So autoadd it.
1498 myautoadd+=[myunadded[x]]
1502 print ">>> Auto-Adding missing digests..."
1503 if "--pretend" in myoptions:
1504 print "(/usr/bin/cvs add "+" ".join(myautoadd)+")"
1507 retval=os.system("/usr/bin/cvs add "+" ".join(myautoadd))
1509 print "!!! Exiting on cvs (shell) error code:",retval
1513 print red("!!! The following files are in your cvs tree but are not added to the master")
1514 print red("!!! tree. Please remove them from the cvs tree or add them to the master tree.")
1523 print "Performing a "+green("cvs -n up")+" with a little magic grep to check for updates."
1524 retval=getstatusoutput("/usr/bin/cvs -n up 2>&1 | egrep '^[^\?] .*' | egrep -v '^. .*/digest-[^/]+|^cvs server: .* -- ignored$'")
1526 mylines=retval[1].split("\n")
1531 if x[0] not in "UPMAR": # Updates,Patches,Modified,Added,Removed
1532 print red("!!! Please fix the following issues reported from cvs: ")+green("(U,P,M,A,R are ok)")
1533 print red("!!! Note: This is a pretend/no-modify pass...")
1537 elif x[0] in ["U","P"]:
1541 print green("Fetching trivial updates...")
1542 if "--pretend" in myoptions:
1543 print "(/usr/bin/cvs up "+" ".join(myupdates)+")"
1546 retval=os.system("/usr/bin/cvs up "+" ".join(myupdates))
1548 print "!!! cvs exited with an error. Terminating."
1552 mycvstree=cvstree.getentries("./",recursive=1)
1553 mychanged=cvstree.findchanged(mycvstree,recursive=1,basedir="./")
1554 mynew=cvstree.findnew(mycvstree,recursive=1,basedir="./")
1555 myremoved=cvstree.findremoved(mycvstree,recursive=1,basedir="./")
1556 if not (mychanged or mynew or myremoved):
1558 print green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\"\n"
1560 print "(Didn't find any changed files...)"
1564 # Manifests need to be regenerated after all other commits, so don't commit
1565 # them now even if they have changed.
1566 mymanifests = [f for f in mychanged if "Manifest" == os.path.basename(f)]
1567 mychanged = [f for f in mychanged if "Manifest" != os.path.basename(f)]
1568 myupdates=mychanged+mynew
1571 headerstring="'\$(Header|Id)"
1572 headerstring+=".*\$'"
1573 for myfile in myupdates:
1574 myout=getstatusoutput("egrep -q "+headerstring+" "+myfile)
1576 myheaders.append(myfile)
1578 print "*",green(str(len(myupdates))),"files being committed...",green(str(len(myheaders))),"have headers that will change."
1579 print "*","Files with headers will cause the manifests to be made and recommited."
1581 print "myupdates:",myupdates
1582 print "myheaders:",myheaders
1585 if commitmessagefile:
1587 f = open(commitmessagefile)
1588 commitmessage = f.read()
1591 except (IOError, OSError), e:
1592 if e.errno == errno.ENOENT:
1593 portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % commitmessagefile)
1596 # We've read the content so the file is no longer needed.
1597 commitmessagefile = None
1598 if not commitmessage:
1599 print "Please enter a CVS commit message at the prompt:"
1600 while not commitmessage:
1602 commitmessage=raw_input(green("> "))
1603 except KeyboardInterrupt:
1606 commitmessage+="\n(Portage version: "+str(portage.VERSION)+")"
1607 except AttributeError:
1608 print "Failed to insert portage version in message!"
1609 commitmessage+="\n(Portage version: Unknown)"
1611 if not manifest1_compat:
1612 myfiles = myupdates + myremoved + mymanifests
1615 filesdirs.add(os.path.join(".", "files"))
1616 elif repolevel in (1, 2):
1618 xs = x.split(os.path.sep)
1619 if len(xs) < 4-repolevel:
1621 xs = xs[0:4-repolevel]
1623 filesdirs.add(os.path.join(*xs))
1625 raise AssertionError("repolevel=%s" % str(repolevel))
1628 dir_path = os.path.join(startdir, x)
1629 if not os.path.isdir(dir_path):
1631 for y in os.listdir(dir_path):
1632 if y.startswith("digest-"):
1633 digest_files.append(os.path.join(x, y))
1636 if "--pretend" in myoptions:
1637 print "(rm %s)" % " ".join(digest_files)
1638 print "(/usr/bin/cvs remove %s)" % " ".join(digest_files)
1640 for x in digest_files:
1641 os.unlink(os.path.join(startdir, x))
1642 retval = spawn(["/usr/bin/cvs", "remove"] + digest_files,
1644 if retval != os.EX_OK:
1645 print "!!! Exiting on cvs (shell) error code:",retval
1647 myremoved.extend(digest_files)
1648 myremoved = list(set(myremoved))
1651 if myupdates or myremoved:
1652 myfiles = myupdates + myremoved
1653 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
1654 mymsg = os.fdopen(fd, "w")
1655 mymsg.write(commitmessage)
1659 print green("Using commit message:")
1660 print green("------------------------------------------------------------------------------")
1662 print green("------------------------------------------------------------------------------")
1666 if "--pretend" in myoptions:
1667 print "(/usr/bin/cvs -q commit -F %s %s)" % \
1668 (commitmessagefile, " ".join(myfiles))
1670 retval = spawn(["/usr/bin/cvs", "-q", "commit",
1671 "-F", commitmessagefile] + myfiles,
1674 os.unlink(commitmessagefile)
1678 print "!!! Exiting on cvs (shell) error code:",retval
1681 # Setup the GPG commands
1682 def gpgsign(filename):
1683 if "PORTAGE_GPG_KEY" not in repoman_settings:
1684 raise portage_exception.MissingParameter("PORTAGE_GPG_KEY is unset!")
1685 if "PORTAGE_GPG_DIR" not in repoman_settings:
1686 if os.environ.has_key("HOME"):
1687 repoman_settings["PORTAGE_GPG_DIR"] = os.path.join(os.environ["HOME"], ".gnupg")
1689 print "Automatically setting PORTAGE_GPG_DIR to",repoman_settings["PORTAGE_GPG_DIR"]
1691 raise portage_exception.MissingParameter("PORTAGE_GPG_DIR is unset!")
1692 gpg_dir = repoman_settings["PORTAGE_GPG_DIR"]
1693 if gpg_dir.startswith("~") and "HOME" in os.environ:
1694 repoman_settings["PORTAGE_GPG_DIR"] = os.path.join(
1695 os.environ["HOME"], gpg_dir[1:].lstrip(os.path.sep))
1696 if not os.access(repoman_settings["PORTAGE_GPG_DIR"], os.X_OK):
1697 raise portage_exception.InvalidLocation(
1698 "Unable to access directory: PORTAGE_GPG_DIR='%s'" % \
1699 repoman_settings["PORTAGE_GPG_DIR"])
1700 gpgcmd = "gpg --sign --clearsign --yes "
1701 gpgcmd+= "--default-key "+repoman_settings["PORTAGE_GPG_KEY"]
1702 if repoman_settings.has_key("PORTAGE_GPG_DIR"):
1703 gpgcmd += " --homedir "+repoman_settings["PORTAGE_GPG_DIR"]
1704 if "--pretend" in myoptions:
1705 print "("+gpgcmd+" "+filename+")"
1707 rValue = os.system(gpgcmd+" "+filename)
1708 if rValue == os.EX_OK:
1709 os.rename(filename+".asc", filename)
1711 raise portage_exception.PortageException("!!! gpg exited with '" + str(rValue) + "' status")
1713 manifest_commit_required = True
1714 if myheaders or myupdates or myremoved or mynew:
1715 myfiles=myheaders+myupdates+myremoved+mynew
1716 for x in range(len(myfiles)-1, -1, -1):
1717 if myfiles[x].count("/") < 4-repolevel:
1720 if repolevel==3: # In a package dir
1721 repoman_settings["O"] = startdir
1722 portage.digestgen([], repoman_settings, manifestonly=1,
1724 elif repolevel==2: # In a category dir
1727 if len(xs) < 4-repolevel:
1733 mydone.append(xs[0])
1734 repoman_settings["O"] = os.path.join(startdir, xs[0])
1735 if not os.path.isdir(repoman_settings["O"]):
1737 portage.digestgen([], repoman_settings, manifestonly=1,
1739 elif repolevel==1: # repo-cvsroot
1740 print green("RepoMan sez:"), "\"You're rather crazy... doing the entire repository.\"\n"
1743 if len(xs) < 4-repolevel:
1747 if "/".join(xs[:2]) in mydone:
1749 mydone.append("/".join(xs[:2]))
1750 repoman_settings["O"] = os.path.join(startdir, xs[0], xs[1])
1751 if not os.path.isdir(repoman_settings["O"]):
1753 portage.digestgen([], repoman_settings, manifestonly=1,
1756 print red("I'm confused... I don't know where I am!")
1759 # Force an unsigned commit when more than one Manifest needs to be signed.
1760 if repolevel < 3 and "sign" in repoman_settings.features:
1761 if "--pretend" in myoptions:
1762 print "(/usr/bin/cvs -q commit -F commitmessagefile)"
1764 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
1765 mymsg = os.fdopen(fd, "w")
1766 mymsg.write(commitmessage)
1767 mymsg.write("\n (Unsigned Manifest commit)")
1769 retval=os.system("/usr/bin/cvs -q commit -F "+commitmessagefile)
1771 os.unlink(commitmessagefile)
1775 print "!!! Exiting on cvs (shell) error code:",retval
1777 manifest_commit_required = False
1780 if "sign" in repoman_settings.features:
1782 myfiles = myupdates + myremoved + mymanifests
1784 if repolevel==3: # In a package dir
1785 repoman_settings["O"] = "."
1786 gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
1787 elif repolevel==2: # In a category dir
1791 if len(xs) < 4-repolevel:
1797 mydone.append(xs[0])
1798 repoman_settings["O"] = os.path.join(".", xs[0])
1799 if not os.path.isdir(repoman_settings["O"]):
1801 gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
1802 elif repolevel==1: # repo-cvsroot
1803 print green("RepoMan sez:"), "\"You're rather crazy... doing the entire repository.\"\n"
1807 if len(xs) < 4-repolevel:
1811 if "/".join(xs[:2]) in mydone:
1813 mydone.append("/".join(xs[:2]))
1814 repoman_settings["O"] = os.path.join(".", xs[0], xs[1])
1815 if not os.path.isdir(repoman_settings["O"]):
1817 gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
1818 except portage_exception.PortageException, e:
1819 portage.writemsg("!!! %s\n" % str(e))
1820 portage.writemsg("!!! Disabled FEATURES='sign'\n")
1823 if manifest_commit_required or signed:
1824 if "--pretend" in myoptions:
1825 print "(/usr/bin/cvs -q commit -F commitmessagefile)"
1827 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
1828 mymsg = os.fdopen(fd, "w")
1829 mymsg.write(commitmessage)
1831 mymsg.write("\n (Signed Manifest commit)")
1833 mymsg.write("\n (Unsigned Manifest commit)")
1835 retval=os.system("/usr/bin/cvs -q commit -F "+commitmessagefile)
1837 os.unlink(commitmessagefile)
1841 print "!!! Exiting on cvs (shell) error code:",retval
1846 print "CVS commit complete."
1848 print "repoman was too scared by not seeing any familiar cvs file that he forgot to commit anything"
1849 print green("RepoMan sez:"), "\"If everyone were like you, I'd be out of business!\"\n"