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 string,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 import portage_exception
41 from portage_manifest import Manifest
42 from portage_exception import ParseError
44 from output import bold, darkgreen, darkred, green, nocolor, red, turquoise, yellow
46 from commands import getstatusoutput
47 from fileinput import input
48 from grp import getgrnam
49 from stat import S_ISDIR, ST_CTIME, ST_GID, ST_MTIME
51 # A sane umask is needed for files that portage creates.
53 repoman_settings = portage.config(local_config=False,
54 config_incrementals=portage_const.INCREMENTALS)
55 repoman_settings.lock()
57 if repoman_settings.get("NOCOLOR", "").lower() in ("yes", "true") or \
58 not sys.stdout.isatty():
62 print exename+": "+txt
68 help(exitstatus=-1,helpfulness=0)
72 def exithandler(signum=None,frame=None):
73 sys.stderr.write("\n"+exename+": Interrupted; exiting...\n")
75 os.kill(0,signal.SIGKILL)
76 signal.signal(signal.SIGINT,exithandler)
78 shortmodes={"ci":"commit"}
80 "scan" : "Scan directory tree for QA issues (default)",
81 "fix" : "Fix simple QA issues (stray digests, missing digests)",
82 "full" : "Scan directory tree for QA issues (full listing)",
83 "help" : "Show this screen",
84 "commit" : "Scan directory tree for QA issues; if OK, commit via cvs",
85 "last" : "Remember report from last run",
86 "lfull" : "Remember report from last run (full listing)"
88 modes=modeshelp.keys()
90 "--commitmsg" : "Adds a commit message via the command line",
91 "--commitmsgfile" : "Adds a commit message from the specified file",
92 "--help" : "Show this screen",
93 "--ignore-arches" : "Ignore arch-specific failures (where arch != host)",
94 "--ignore-masked" : "Ignore masked packages (not allowed with commit mode)",
95 "--pretend" : "Don't commit or fix anything; just show what would be done",
96 "--quiet" : "Be less verbose about extraneous info",
97 "--verbose" : "Displays every package name while checking",
98 "--version" : "Show version info",
99 "--xmlparse" : "Forces the metadata.xml parse check to be carried out"
101 repoman_shortoptions={
103 "-i" : "--ignore-masked",
104 "-I" : "--ignore-arches",
105 "-m" : "--commitmsg",
106 "-M" : "--commitmsgfile",
113 repoman_shortoptions_rev=dict([(v,k) for (k,v) in repoman_shortoptions.items()])
114 options=repoman_options.keys()
117 "CVS/Entries.IO_error":"Attempting to commit, and an IO error was encountered access the Entries file",
118 "digest.partial":"Digest files do not contain all corresponding URI elements",
119 "digest.assumed":"Existing digest must be assumed correct (Package level only)",
120 "digestentry.unused":"Digest/Manifest entry has no matching SRC_URI entry",
121 "digest.fail":"Digest does not match the specified local file",
122 "digest.stray":"Digest files that do not have a corresponding ebuild",
123 "digest.missing":"Digest files that are missing (ebuild exists, digest doesn't)",
124 "digest.disjointed":"Digests not added to cvs when the matching ebuild has been added",
125 "digest.notadded":"Digests that exist but have not been added to cvs",
126 "digest.unmatch":"Digests which are incomplete (please check if your USE/ARCH includes all files)",
127 "ebuild.invalidname":"Ebuild files with a non-parseable or syntactically incorrect name (or using 2.1 versioning extensions)",
128 "ebuild.namenomatch":"Ebuild files that do not have the same name as their parent directory",
129 "changelog.missing":"Missing ChangeLog files",
130 "ebuild.disjointed":"Ebuilds not added to cvs when the matching digest has been added",
131 "ebuild.notadded":"Ebuilds that exist but have not been added to cvs",
132 "changelog.notadded":"ChangeLogs that exist but have not been added to cvs",
133 "filedir.missing":"Package lacks a files directory",
134 "file.executable":"Ebuilds, digests, metadata.xml, Manifest, and ChangeLog do note need the executable bit",
135 "file.size":"Files in the files directory must be under 20k",
136 "file.name":"File/dir name must be composed of only the following chars: %s " % allowed_filename_chars,
137 "file.UTF8":"File is not UTF8 compliant",
138 "KEYWORDS.missing":"Ebuilds that have a missing or empty KEYWORDS variable",
139 "KEYWORDS.stupid":"Ebuilds that use KEYWORDS=-* instead of package.mask",
140 "LICENSE.missing":"Ebuilds that have a missing or empty LICENSE variable",
141 "DESCRIPTION.missing":"Ebuilds that have a missing or empty DESCRIPTION variable",
142 "SLOT.missing":"Ebuilds that have a missing or empty SLOT variable",
143 "HOMEPAGE.missing":"Ebuilds that have a missing or empty HOMEPAGE variable",
144 "DEPEND.bad":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds)",
145 "RDEPEND.bad":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds)",
146 "PDEPEND.bad":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds)",
147 "DEPEND.badmasked":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds)",
148 "RDEPEND.badmasked":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds)",
149 "PDEPEND.badmasked":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds)",
150 "DEPEND.badindev":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds) in developing arch",
151 "RDEPEND.badindev":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds) in developing arch",
152 "PDEPEND.badindev":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds) in developing arch",
153 "DEPEND.badmaskedindev":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds) in developing arch",
154 "RDEPEND.badmaskedindev":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds) in developing arch",
155 "PDEPEND.badmaskedindev":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds) in developing arch",
156 "DEPEND.syntax":"Syntax error in DEPEND (usually an extra/missing space/parenthesis)",
157 "RDEPEND.syntax":"Syntax error in RDEPEND (usually an extra/missing space/parenthesis)",
158 "PDEPEND.syntax":"Syntax error in PDEPEND (usually an extra/missing space/parenthesis)",
159 "LICENSE.syntax":"Syntax error in LICENSE (usually an extra/missing space/parenthesis)",
160 "PROVIDE.syntax":"Syntax error in PROVIDE (usually an extra/missing space/parenthesis)",
161 "ebuild.syntax":"Error generating cache entry for ebuild; typically caused by ebuild syntax error",
162 "ebuild.output":"A simple sourcing of the ebuild produces output; this breaks ebuild policy.",
163 "ebuild.nesteddie":"Placing 'die' inside ( ) prints an error, but doesn't stop the ebuild.",
164 "variable.readonly":"Assigning a readonly variable",
165 "LIVEVCS.stable":"This ebuild is a live checkout from a VCS but has stable keywords.",
166 "IUSE.invalid":"This ebuild has a variable in IUSE that is not in the use.desc or use.local.desc file",
167 "LICENSE.invalid":"This ebuild is listing a license that doesnt exist in portages license/ dir.",
168 "KEYWORDS.invalid":"This ebuild contains KEYWORDS that are not listed in profiles/arch.list or for which no valid profile was found",
169 "ebuild.nostable":"There are no ebuilds that are marked as stable for your ARCH",
170 "ebuild.allmasked":"All ebuilds are masked for this package (Package level only)",
171 "ebuild.majorsyn":"This ebuild has a major syntax error that may cause the ebuild to fail partially or fully",
172 "ebuild.minorsyn":"This ebuild has a minor syntax error that contravenes gentoo coding style",
173 "ebuild.badheader":"This ebuild has a malformed header",
174 "metadata.missing":"Missing metadata.xml files",
175 "metadata.bad":"Bad metadata.xml files",
176 "virtual.versioned":"PROVIDE contains virtuals with versions",
177 "virtual.exists":"PROVIDE contains existing package names",
178 "virtual.unavailable":"PROVIDE contains a virtual which contains no profile default",
179 "usage.obsolete":"The ebuild makes use of an obsolete construct"
182 qacats = qahelp.keys()
187 "changelog.notadded",
196 "digestentry.unused",
197 "DEPEND.badmasked","RDEPEND.badmasked","PDEPEND.badmasked",
198 "DEPEND.badindev","RDEPEND.badindev","PDEPEND.badindev",
199 "DEPEND.badmaskedindev","RDEPEND.badmaskedindev","PDEPEND.badmaskedindev",
209 "virtual.unavailable",
214 missingvars=["KEYWORDS","LICENSE","DESCRIPTION","HOMEPAGE","SLOT"]
215 allvars=portage.auxdbkeys
217 commitmessagefile=None
218 for x in missingvars:
221 print "* missingvars values need to be added to qahelp ("+x+")"
226 ven_cat = r'[\w0-9-]+' # Category
227 ven_nam = r'([+a-z0-9-]+(?:[+_a-z0-9-]*[+a-z0-9-]+)*)' # Name
228 ven_ver = r'((?:\d+\.)*\d+[a-z]?)' # Version
229 ven_suf = r'(_(alpha\d*|beta\d*|pre\d*|rc\d*|p\d+))?' # Suffix
230 ven_rev = r'(-r\d+)?' # Revision
232 ven_string=ven_cat+'/'+ven_nam+'-'+ven_ver+ven_suf+ven_rev
233 valid_ebuild_name_re=re.compile(ven_string+'$', re.I)
234 valid_ebuild_filename_re=re.compile(ven_string+'\.ebuild$', re.I)
239 def valid_ebuild_name(name):
240 """(name) --- Checks to ensure that the package name meets portage specs.
241 Return 1 if valid, 0 if not."""
242 # Handle either a path to the ebuild, or cat/pkg-ver string
243 if (len(name) > 7) and (name[-7:] == ".ebuild"):
244 if valid_ebuild_filename_re.match(name):
247 if valid_ebuild_name_re.match(name):
253 print exename+" "+version
256 def help(exitstatus=1,helpfulness=1):
261 print green(exename+" "+version)
262 print " \"Quality is job zero.\""
263 print " Copyright 1999-2006 Gentoo Foundation"
264 print " Distributed under the terms of the GNU General Public License v2"
266 print bold(" Usage:"),turquoise(exename),"[",green("options"),"] [",green("mode"),"]"
268 print bold(" Modes:"),turquoise("scan (default)"),
270 print "|",turquoise(x),
273 print " "+green(string.ljust("Options",20)+" Description")
275 if repoman_shortoptions_rev.has_key(x):
276 shopt=repoman_shortoptions_rev[x]+", "+x
279 print " "+string.ljust(shopt,20),repoman_options[x]
281 print " "+green(string.ljust("Modes",20)+" Description")
283 print " "+string.ljust(x,20),modeshelp[x]
286 print " "+green(string.ljust("QA keyword",20)+" Description")
288 print " "+string.ljust(x,20),qahelp[x]
290 if (exitstatus != -1):
297 #Retrieve and unpickle stats and fails from saved files
298 savedf=open('/var/cache/edb/repo.stats','r')
299 stats = pickle.load(savedf)
301 savedf=open('/var/cache/edb/repo.fails','r')
302 fails = pickle.load(savedf)
304 except SystemExit, e:
305 raise # Need to propogate this
307 err("Error retrieving last repoman run data; exiting.")
309 #dofail will be set to 1 if we have failed in at least one non-warning category
311 #dowarn will be set to 1 if we tripped any warnings
313 #dofull will be set if we should print a "repoman full" informational message
317 print green("RepoMan remembers...")
322 if x not in qawarnings:
327 print " "+string.ljust(x,20),
329 print green(`stats[x]`)
331 elif x in qawarnings:
332 print yellow(`stats[x]`)
334 print red(`stats[x]`)
346 print bold("Note: type \"repoman lfull\" for a complete listing of repomans last run.")
348 if dowarn and not dofail:
349 print green("RepoMan sez:"),"\"You only gave me a partial QA payment last time?\n I took it, but I wasn't happy.\""
351 print green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\""
359 while x < len(sys.argv):
360 if sys.argv[x] in shortmodes.keys():
361 sys.argv[x]=shortmodes[sys.argv[x]]
362 elif sys.argv[x] in repoman_shortoptions.keys():
363 sys.argv[x] = repoman_shortoptions[sys.argv[x]]
364 if sys.argv[x] in modes:
368 err("Please specify either \""+mymode+"\" or \""+sys.argv[x]+"\", but not both.")
369 elif sys.argv[x] in options:
371 if (optionx=="--commitmsg") and (len(sys.argv)>=(x+1)):
372 commitmessage=sys.argv[x+1]
374 elif (optionx=="--commitmsgfile") and (len(sys.argv)>=(x+1)):
375 commitmessagefile=sys.argv[x+1]
377 elif (optionx=="--verbose"):
379 elif (optionx=="--quiet"):
381 elif optionx not in myoptions:
382 myoptions.append(optionx)
384 err_help("\""+sys.argv[x]+"\" is not a valid mode or option.")
388 if mymode=="help" or ("--help" in myoptions):
390 if ("--version" in myoptions):
392 if mymode=="last" or (mymode=="lfull"):
394 if mymode == "commit":
395 while "--ignore-masked" in myoptions:
396 myoptions.remove("--ignore-masked")
400 if os.path.isdir("CVS"):
401 if "cvs" not in repoman_settings.features:
404 print red('!!! You do not have ')+bold('FEATURES="cvs" ')+red("enabled...")
405 print red("!!! ")+bold("Adding \"cvs\" to FEATURES")
407 os.environ["FEATURES"]=repoman_settings["FEATURES"]+" cvs"
410 if not "--pretend" in myoptions and not isCvs:
412 print darkgreen("Not in a CVS repository; enabling pretend mode.")
413 myoptions.append("--pretend");
416 def have_profile_dir(path, maxdepth=3):
417 while path != "/" and maxdepth:
418 if os.path.exists(path + "/profiles/package.mask"):
420 path = os.path.normpath(path + "/..")
426 if "PWD" in os.environ and os.environ["PWD"] != mydir and \
427 os.path.realpath(os.environ["PWD"]) == mydir:
428 # getcwd() returns the canonical path but that makes it hard for repoman to
429 # orient itself if the user has symlinks in their portage tree structure.
430 # We use os.environ["PWD"], if available, to get the non-canonical path of
431 # the current working directory (from the shell).
432 mydir = os.path.normpath(os.environ["PWD"])
436 for overlay in repoman_settings["PORTDIR_OVERLAY"].split():
437 if overlay[-1] != "/":
439 if mydir.startswith(overlay):
440 portdir_overlay = overlay
441 subdir = mydir[len(overlay):]
442 if subdir and subdir[-1] != "/":
444 if have_profile_dir(mydir, subdir.count("/")):
445 portdir = portdir_overlay
448 if not portdir_overlay:
449 if (repoman_settings["PORTDIR"] + os.path.sep).startswith(mydir):
450 portdir_overlay = repoman_settings["PORTDIR"]
452 portdir_overlay = have_profile_dir(mydir)
453 portdir = portdir_overlay
455 if not portdir_overlay:
456 sys.stderr.write("Repoman is unable to determine PORTDIR or PORTDIR_OVERLAY from" + \
457 " the current\nworking directory.\n")
461 portdir = repoman_settings["PORTDIR"]
463 if portdir[-1] == "/":
464 portdir = portdir[:-1]
465 if portdir_overlay[-1] == "/":
466 portdir_overlay = portdir_overlay[:-1]
468 os.environ["PORTDIR"] = portdir
469 if portdir_overlay != portdir:
470 os.environ["PORTDIR_OVERLAY"] = portdir_overlay
472 os.environ["PORTDIR_OVERLAY"] = ""
475 print "\nSetting paths:"
476 print "PORTDIR = \""+os.environ["PORTDIR"]+"\""
477 print "PORTDIR_OVERLAY = \""+os.environ["PORTDIR_OVERLAY"]+"\""
479 # Now that PORTDIR_OVERLAY is properly overridden, create the portdb.
480 repoman_settings = portage.config(local_config=False,
481 config_incrementals=portage_const.INCREMENTALS)
482 trees = portage.create_trees()
483 trees["/"]["porttree"].settings = repoman_settings
484 portdb = trees["/"]["porttree"].dbapi
485 portdb.mysettings = repoman_settings
486 # dep_zapdeps looks at the vardbapi, but it shouldn't for repoman.
487 trees["/"]["vartree"].dbapi = portage.fakedbapi(settings=repoman_settings)
490 myreporoot = os.path.basename(portdir_overlay)
491 myreporoot += mydir[len(portdir_overlay):-1]
493 reposplit=string.split(myreporoot,"/")
494 repolevel=len(reposplit)
496 # check if it's in $PORTDIR/$CATEGORY/$PN , otherwise bail if commiting.
497 # Reason for this is if they're trying to commit in just $FILESDIR/*, the Manifest needs updating.
498 # this check ensure that repoman knows where it is, and the manifest recommit is at least possible.
499 if mymode == "commit" and repolevel not in [1,2,3]:
500 print red("***")+" Commit attempts *must* be from within a cvs co, category, or package directory."
501 print red("***")+" Attempting to commit from a packages files directory will be blocked for instance."
502 print red("***")+" This is intended behaviour, to ensure the manifest is recommited for a package."
504 err("Unable to identify level we're commiting from for %s" % string.join(reposplit,'/'))
508 for x in range(0,repolevel-1):
514 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.")
515 if "--pretend" in myoptions:
516 print green("\nRepoMan does a once-over of the neighborhood...")
518 print green("\nRepoMan scours the neighborhood...")
520 def parse_use_local_desc(mylines, usedict=None):
521 """returns a dict of the form {cpv:set(flags)}"""
527 if not l or l.startswith("#"):
529 mysplit = l.split(None, 1)
532 mysplit = mysplit[0].split(":")
533 if len(mysplit) != 2:
534 raise ParseError("line %d: Malformed input: '%s'" % \
535 (lineno, l.rstrip("\n")))
536 usedict.setdefault(mysplit[0], set())
537 usedict[mysplit[0]].add(mysplit[1])
540 # retreive local USE list
543 f = open(os.path.join(portdir, "profiles", "use.local.desc"))
544 parse_use_local_desc(f, luselist)
546 except (IOError, OSError, ParseError), e:
547 print >> sys.stderr, str(e)
548 err("Couldn't read from use.local.desc")
550 if portdir_overlay != portdir:
551 filename = os.path.join(portdir_overlay, "profiles", "use.local.desc")
552 if os.path.exists(filename):
555 parse_use_local_desc(f, luselist)
557 except (IOError, OSError, ParseError), e:
558 print >> sys.stderr, str(e)
559 err("Couldn't read from '%s'" % filename)
562 # setup a uselist from portage
565 uselist=portage.grabfile(portdir+"/profiles/use.desc")
566 for l in range(0,len(uselist)):
567 uselist[l]=string.split(uselist[l])[0]
568 for var in string.split(repoman_settings["USE_EXPAND"]):
569 vardescs = portage.grabfile(portdir+"/profiles/desc/"+var.lower()+".desc")
570 for l in range(0, len(vardescs)):
571 uselist.append(var.lower() + "_" + string.split(vardescs[l])[0])
572 except SystemExit, e:
573 raise # Need to propogate this
575 err("Couldn't read USE flags from use.desc")
577 # retrieve a list of current licenses in portage
578 liclist = set(portage.listdir(os.path.join(portdir, "licenses")))
580 err("Couldn't find licenses?")
581 if portdir_overlay != portdir:
582 liclist.update(portage.listdir(os.path.join(portdir_overlay, "licenses")))
584 # retrieve list of offical keywords
585 kwlist = set(portage.grabfile(os.path.join(portdir, "profiles", "arch.list")))
587 err("Couldn't read KEYWORDS from arch.list")
589 if portdir_overlay != portdir:
590 kwlist.update(portage.grabfile(
591 os.path.join(portdir_overlay, "profiles", "arch.list")))
595 #we are inside a category directory
597 if catdir not in repoman_settings.categories:
599 mydirlist=os.listdir(startdir)
601 if x == "CVS" or x.startswith("."):
603 if os.path.isdir(startdir+"/"+x):
604 scanlist.append(catdir+"/"+x)
606 for x in repoman_settings.categories:
607 if not os.path.isdir(startdir+"/"+x):
609 for y in os.listdir(startdir+"/"+x):
610 if y == "CVS" or y.startswith("."):
612 if os.path.isdir(startdir+"/"+x+"/"+y):
613 scanlist.append(x+"/"+y)
615 catdir = reposplit[-2]
616 if catdir not in repoman_settings.categories:
618 scanlist.append(catdir+"/"+reposplit[-1])
621 descfile=portdir+"/profiles/profiles.desc"
622 if os.path.exists(descfile):
623 for x in portage.grabfile(descfile):
628 print "wrong format: \""+red(x)+"\" in "+descfile
630 if not os.path.isdir(portdir+"/profiles/"+arch[1]):
631 print "Invalid "+arch[2]+" profile ("+arch[1]+") for arch "+arch[0]
633 if profiles.has_key(arch[0]):
634 profiles[arch[0]]+= [[arch[1], arch[2]]]
636 profiles[arch[0]] = [[arch[1], arch[2]]]
638 for x in repoman_settings.archlist():
641 if not profiles.has_key(x):
642 print red("\""+x+"\" doesn't have a valid profile listed in profiles.desc.")
643 print red("You need to either \"cvs update\" your profiles dir or follow this")
644 print red("up with the "+x+" team.")
647 print red("profiles.desc does not exist: "+descfile)
648 print red("You need to do \"cvs update\" in profiles dir.")
659 xmllint_capable = False
660 if getstatusoutput('which xmllint')[0] != 0:
661 print red("!!! xmllint not found. Can't check metadata.xml.\n")
662 if "--xmlparse" in myoptions or repolevel==3:
663 print red("!!!")+" sorry, xmllint is needed. failing\n"
666 #hardcoded paths/urls suck. :-/
670 # if it's been over a week since fetching (or the system clock is fscked), grab an updated copy of metadata.dtd
671 # clock is fscked or it's been a week. time to grab a new one.
672 ct=os.stat(portage.CACHE_PATH + '/metadata.dtd')[ST_CTIME]
673 if abs(time.time() - ct) > (60*60*24*7):
674 # don't trap the exception, we're watching for errno 2 (file not found), anything else is a bug.
679 except (OSError,IOError), e:
681 print red("!!!")+" caught exception '%s' for %s/metadata.dtd, bailing" % (str(e), portage.CACHE_PATH)
686 print green("***")+" the local copy of metadata.dtd needs to be refetched, doing that now"
689 if os.path.exists(repoman_settings["DISTDIR"]+'/metadata.dtd'):
690 os.remove(repoman_settings["DISTDIR"]+'/metadata.dtd')
691 val=portage.fetch(['http://www.gentoo.org/dtd/metadata.dtd'],repoman_settings,fetchonly=0, \
695 os.remove(portage.CACHE_PATH+'/metadata.dtd')
696 shutil.copy(repoman_settings["DISTDIR"]+'/metadata.dtd',portage.CACHE_PATH+'/metadata.dtd')
697 os.chown(portage.CACHE_PATH+'/metadata.dtd',os.getuid(),portage.portage_gid)
698 os.chmod(portage.CACHE_PATH+'/metadata.dtd',0664)
701 except SystemExit, e:
702 raise # Need to propogate this
705 print red("!!!")+" attempting to fetch 'http://www.gentoo.org/dtd/metadata.dtd', caught"
706 print red("!!!")+" exception '%s' though." % str(e)
709 print red("!!!")+" fetching new metadata.dtd failed, aborting"
711 #this can be problematic if xmllint changes their output
715 def x11_deprecation_check(depstr):
716 if depstr.find("virtual/x11") == -1:
718 depsplit = depstr.split()
721 for token in depsplit:
731 if token.find("virtual/x11") != -1 and (not ok_stack or not ok_stack[-1]):
738 #ebuilds and digests added to cvs respectively.
740 print "checking package " + x
743 catdir,pkgdir=x.split("/")
744 checkdir=repodir+"/"+x
745 checkdirlist=os.listdir(checkdir)
747 for y in checkdirlist:
748 if y[-7:]==".ebuild":
749 ebuildlist.append(y[:-7])
750 if y in ["Manifest","ChangeLog","metadata.xml"]:
751 if os.stat(checkdir+"/"+y)[0] & 0x0248:
752 stats["file.executable"] += 1
753 fails["file.executable"].append(checkdir+"/"+y)
756 for y in checkdirlist:
757 for c in y.strip(os.path.sep):
758 if c not in allowed_filename_chars_set:
759 stats["file.name"] += 1
760 fails["file.name"].append("%s/%s: char '%s'" % (checkdir, y, c))
763 if not (y in ("ChangeLog", "metadata.xml") or y.endswith(".ebuild")):
767 for l in codecs.open(checkdir+"/"+y, "r", "utf8"):
769 except UnicodeDecodeError, ue:
770 stats["file.UTF8"] += 1
771 s = ue.object[:ue.start]
775 s = s[s.rfind("\n") + 1:]
776 fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s))
780 mystat=os.stat(checkdir+"/files")[0]
781 if len(ebuildlist) and not S_ISDIR(mystat):
783 except SystemExit, e:
784 raise # Need to propogate this
786 stats["filedir.missing"] += 1
787 fails["filedir.missing"].append(checkdir)
790 myf=open(checkdir+"/CVS/Entries","r")
795 splitl=l[1:].split("/")
798 if splitl[0][-7:]==".ebuild":
799 eadded.append(splitl[0][:-7])
802 stats["CVS/Entries.IO_error"] += 1
803 fails["CVS/Entries.IO_error"].append(checkdir+"/CVS/Entries")
807 myf=open(checkdir+"/files/CVS/Entries","r")
812 splitl=l[1:].split("/")
815 if splitl[0][:7]=="digest-":
816 dadded.append(splitl[0][7:])
819 stats["CVS/Entries.IO_error"] += 1
820 fails["CVS/Entries.IO_error"].append(checkdir+"/files/CVS/Entries")
823 if mymode == "commit":
824 repoman_settings["O"] = checkdir
826 [], repoman_settings, manifestonly=1, myportdb=portdb)
828 mf = Manifest(checkdir, repoman_settings["DISTDIR"])
829 mydigests=mf.getTypeDigests("DIST")
832 if os.path.exists(checkdir+"/files"):
833 filesdirlist=os.listdir(checkdir+"/files")
834 for y in filesdirlist:
836 if y[7:] not in dadded:
837 #digest not added to cvs
838 stats["digest.notadded"]=stats["digest.notadded"]+1
839 fails["digest.notadded"].append(x+"/files/"+y)
841 stats["digest.disjointed"]=stats["digest.disjointed"]+1
842 fails["digest.disjointed"].append(x+"/files/"+y)
844 if os.stat(checkdir+"/files/"+y)[0] & 0x0248:
845 stats["file.executable"] += 1
846 fails["file.executable"].append(x+"/files/"+y)
848 mykey = catdir + "/" + y[7:]
849 if y[7:] not in ebuildlist:
852 if "--pretend" in myoptions:
853 print "(cd "+repodir+"/"+x+"/files; cvs rm -f "+y+")"
855 os.system("(cd "+repodir+"/"+x+"/files; cvs rm -f "+y+")")
857 stats["digest.stray"]=stats["digest.stray"]+1
858 fails["digest.stray"].append(x+"/files/"+y)
861 myuris, myfiles = portdb.getfetchlist(mykey, all=True)
862 myfiles_all.extend(myfiles)
866 myubn = os.path.basename(myu)
867 if myubn not in uri_dict:
868 uri_dict[myubn] = [myu]
870 uri_dict[myubn] += [myu]
873 myff = repoman_settings["DISTDIR"] + "/" + myf
874 if not mydigests.has_key(myf):
875 uri_settings = portage.config(clone=repoman_settings)
877 if not portage.fetch(uri_dict[myf], uri_settings):
878 stats["digest.unmatch"] += 1
879 fails["digest.unmatch"].append(y+"::"+myf)
881 eb_name = portdb.findname2(mykey)[0]
882 portage.doebuild(eb_name, "digest", "/",
883 uri_settings, tree="porttree",
886 stats["digest.partial"] += 1
887 fails["digest.partial"].append(y+"::"+myf)
888 elif "assume-digests" not in repoman_settings.features:
889 if os.path.exists(myff):
890 if not portage_checksum.verify_all(myff, mydigests[myf]):
891 stats["digest.fail"] += 1
892 fails["digest.fail"].append(y+"::"+myf)
894 stats["digest.assumed"] += 1
895 fails["digest.assumed"].append(y+"::"+myf)
897 # recurse through files directory
898 # use filesdirlist as a stack, appending directories as needed so people can't hide > 20k files in a subdirectory.
900 y = filesdirlist.pop(0)
902 mystat = os.stat(checkdir+"/files/"+y)
905 # don't worry about it. it likely was removed via fix above.
909 if S_ISDIR(mystat.st_mode):
912 for z in os.listdir(checkdir+"/files/"+y):
915 filesdirlist.append(y+"/"+z)
916 # current policy is no files over 20k, this is the check.
917 elif mystat.st_size > 20480:
918 stats["file.size"] += 1
919 fails["file.size"].append("("+ str(mystat.st_size/1024) + "K) "+x+"/files/"+y)
921 for c in os.path.basename(y.rstrip(os.path.sep)):
922 if c not in allowed_filename_chars_set:
923 stats["file.name"] += 1
924 fails["file.name"].append("%s/files/%s: char '%s'" % (checkdir, y, c))
927 for entry in mydigests.keys():
928 if entry not in myfiles_all:
929 stats["digestentry.unused"] += 1
930 fails["digestentry.unused"].append(checkdir+"::"+entry)
933 if "ChangeLog" not in checkdirlist:
934 stats["changelog.missing"]+=1
935 fails["changelog.missing"].append(x+"/ChangeLog")
937 #metadata.xml file check
938 if "metadata.xml" not in checkdirlist:
939 stats["metadata.missing"]+=1
940 fails["metadata.missing"].append(x+"/metadata.xml")
941 #metadata.xml parse check
943 #Only carry out if in package directory or check forced
945 st=getstatusoutput("xmllint --nonet --noout --dtdvalid %s/metadata.dtd %s/metadata.xml" % (portage.CACHE_PATH, checkdir))
947 for z in st[1].split("\n"):
949 stats["metadata.bad"]+=1
950 fails["metadata.bad"].append(x+"/metadata.xml")
955 if os.stat(checkdir+"/"+y+".ebuild")[0] & 0x0248:
956 stats["file.executable"] += 1
957 fails["file.executable"].append(x+"/"+y+".ebuild")
959 #ebuild not added to cvs
960 stats["ebuild.notadded"]=stats["ebuild.notadded"]+1
961 fails["ebuild.notadded"].append(x+"/"+y+".ebuild")
963 stats["ebuild.disjointed"]=stats["ebuild.disjointed"]+1
964 fails["ebuild.disjointed"].append(x+"/"+y+".ebuild")
965 if not os.path.exists(checkdir+"/files/digest-"+y):
967 if "--pretend" in myoptions:
968 print "You will need to run:"
969 print " /usr/bin/ebuild "+repodir+"/"+x+"/"+y+".ebuild digest"
971 retval=os.system("/usr/bin/ebuild "+repodir+"/"+x+"/"+y+".ebuild digest")
973 print "!!! Exiting on ebuild digest (shell) error code:",retval
976 stats["digest.missing"]=stats["digest.missing"]+1
977 fails["digest.missing"].append(x+"/files/digest-"+y)
978 myesplit=portage.pkgsplit(y)
979 if myesplit is None or not valid_ebuild_name(x.split("/")[0]+"/"+y):
980 stats["ebuild.invalidname"]=stats["ebuild.invalidname"]+1
981 fails["ebuild.invalidname"].append(x+"/"+y+".ebuild")
983 elif myesplit[0]!=pkgdir:
984 print pkgdir,myesplit[0]
985 stats["ebuild.namenomatch"]=stats["ebuild.namenomatch"]+1
986 fails["ebuild.namenomatch"].append(x+"/"+y+".ebuild")
990 zip(allvars, portdb.aux_get(os.path.join(catdir, y), allvars)))
992 stats["ebuild.syntax"]=stats["ebuild.syntax"]+1
993 fails["ebuild.syntax"].append(x+"/"+y+".ebuild")
996 stats["ebuild.output"]=stats["ebuild.output"]+1
997 fails["ebuild.output"].append(x+"/"+y+".ebuild")
1000 # Test for negative logic and bad words in the RESTRICT var.
1001 #for x in myaux[allvars.index("RESTRICT")].split():
1002 # if x.startswith("no"):
1003 # print "Bad RESTRICT value: %s" % x
1005 myaux["PROVIDE"] = portage_dep.use_reduce(portage_dep.paren_reduce(myaux["PROVIDE"]), matchall=1)
1006 myaux["PROVIDE"] = " ".join(portage.flatten(myaux["PROVIDE"]))
1007 for myprovide in myaux["PROVIDE"].split():
1008 prov_cp = portage.dep_getkey(myprovide)
1009 if prov_cp != myprovide:
1010 stats["virtual.versioned"]+=1
1011 fails["virtual.versioned"].append(x+"/"+y+".ebuild: "+myprovide)
1012 prov_pkg = portage.dep_getkey(
1013 portage.best(portdb.xmatch("match-all", prov_cp)))
1014 if prov_cp == prov_pkg:
1015 stats["virtual.exists"]+=1
1016 fails["virtual.exists"].append(x+"/"+y+".ebuild: "+prov_cp)
1018 for pos in range(0,len(missingvars)):
1019 if not myaux[missingvars[pos]]:
1020 myqakey=missingvars[pos]+".missing"
1021 stats[myqakey]=stats[myqakey]+1
1022 fails[myqakey].append(x+"/"+y+".ebuild")
1024 # KEYWORDS="-*" is a stupid replacement for package.mask and screws general KEYWORDS semantics
1025 if "-*" in myaux["KEYWORDS"].split():
1027 for kw in myaux["KEYWORDS"].split():
1033 stats["KEYWORDS.stupid"] += 1
1034 fails["KEYWORDS.stupid"].append(x+"/"+y+".ebuild")
1037 Ebuilds that inherit a "Live" eclasss (darcs,subversion,git,cvs,etc..) should
1038 not be allowed to be marked stable
1040 if set(["darcs","cvs","subversion","git"]).intersection(myaux["INHERITED"].split()):
1041 bad_stable_keywords = [keyword for keyword in myaux["KEYWORDS"].split() if not keyword.startswith("~") and not keyword.startswith("-")]
1042 if bad_stable_keywords:
1043 stats["LIVEVCS.stable"] += 1
1044 fails["LIVEVCS.stable"].append(x+"/"+y+".ebuild with stable keywords:%s " % bad_stable_keywords)
1045 del keyword, bad_stable_keywords
1047 if "--ignore-arches" in myoptions:
1048 arches = [[repoman_settings["ARCH"], repoman_settings["ARCH"],
1049 repoman_settings["ACCEPT_KEYWORDS"].split()]]
1052 for keyword in myaux["KEYWORDS"].split():
1053 if (keyword[0]=="-"):
1055 elif (keyword[0]=="~"):
1056 arches.append([keyword, keyword[1:], [keyword[1:], keyword]])
1058 arches.append([keyword, keyword, [keyword]])
1061 baddepsyntax = False
1062 badlicsyntax = False
1063 badprovsyntax = False
1064 catpkg = catdir+"/"+y
1065 myiuse = set(repoman_settings.archlist())
1066 for myflag in myaux["IUSE"].split():
1067 if myflag.startswith("+"):
1071 type_list, badsyntax = [], []
1072 for mytype in ("DEPEND", "RDEPEND", "PDEPEND", "LICENSE", "PROVIDE"):
1073 mydepstr = myaux[mytype]
1075 if (string.find(mydepstr, " ?") != -1):
1076 badsyntax.append("'?' preceded by space")
1079 # Missing closing parenthesis will result in a ValueError
1080 mydeplist = portage_dep.paren_reduce(mydepstr)
1081 # Missing opening parenthesis will result in a final "" element
1082 if "" in mydeplist or "(" in mydeplist:
1085 badsyntax.append("parenthesis mismatch")
1089 portage_dep.use_reduce(mydeplist, excludeall=myiuse)
1090 except portage_exception.InvalidDependString, e:
1091 badsyntax.append(str(e))
1093 for token in ("||", "(", ")"):
1094 if mydepstr.startswith(token+" "):
1095 myteststr = mydepstr[len(token):]
1097 myteststr = mydepstr
1098 if myteststr.endswith(" "+token):
1099 myteststr = myteststr[:-len(token)]
1100 while myteststr.find(" "+token+" ") != -1:
1101 myteststr = " ".join(myteststr.split(" "+token+" ", 1))
1102 if myteststr.find(token) != -1:
1103 badsyntax.append("'%s' not separated by space" % (token))
1106 if mytype in ("DEPEND", "RDEPEND", "PDEPEND"):
1107 for token in filter(lambda x: not (x.endswith("?") or x.strip() in ("||", "&&", "(", ")")), mydepstr.split()):
1108 if not "/" in token or \
1109 ":" in token and myaux["EAPI"] == "0":
1110 badsyntax.append("'%s' not a valid atom" % token)
1112 type_list.extend([mytype] * (len(badsyntax) - len(type_list)))
1114 for m,b in zip(type_list, badsyntax):
1115 stats[m+".syntax"] += 1
1116 fails[m+".syntax"].append(catpkg+".ebuild "+m+": "+b)
1118 badlicsyntax = len(filter(lambda x:x=="LICENSE", type_list))
1119 badprovsyntax = len(filter(lambda x:x=="PROVIDE", type_list))
1120 baddepsyntax = len(type_list) != badlicsyntax + badprovsyntax
1121 badlicsyntax = badlicsyntax > 0
1122 badprovsyntax = badprovsyntax > 0
1124 if not baddepsyntax:
1125 if x11_deprecation_check(" ".join([myaux["DEPEND"], myaux["RDEPEND"], myaux["PDEPEND"]])):
1126 stats["usage.obsolete"] += 1
1127 fails["usage.obsolete"].append("%s/%s.ebuild: not migrated to modular X" % (x, y))
1129 for keyword,arch,groups in arches:
1131 if not profiles.has_key(arch):
1132 # A missing profile will create an error further down
1133 # during the KEYWORDS verification.
1136 for prof in profiles[arch]:
1138 profdir = portdir+"/profiles/"+prof[0]
1140 if prof[0] in arch_caches:
1141 dep_settings = arch_caches[prof[0]]
1143 dep_settings = portage.config(
1144 config_profile_path=profdir,
1145 config_incrementals=portage_const.INCREMENTALS,
1147 arch_caches[prof[0]] = dep_settings
1150 # Protect ACCEPT_KEYWORDS from config.regenerate()
1152 dep_settings.incrementals.remove("ACCEPT_KEYWORDS")
1156 trees["/"]["porttree"].settings = dep_settings
1157 portdb.mysettings = dep_settings
1158 # for package.use.mask support inside dep_check
1159 dep_settings.setcpv("/".join((catdir, y)))
1160 dep_settings["ACCEPT_KEYWORDS"] = " ".join(groups)
1161 # just in case, prevent config.reset() from nuking these.
1162 dep_settings.backup_changes("ACCEPT_KEYWORDS")
1164 for myprovide in myaux["PROVIDE"].split():
1165 prov_cp = portage.dep_getkey(myprovide)
1166 if prov_cp not in dep_settings.getvirtuals():
1167 stats["virtual.unavailable"]+=1
1168 fails["virtual.unavailable"].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+prov_cp)
1170 if not baddepsyntax:
1171 ismasked = os.path.join(catdir, y) not in \
1172 portdb.xmatch("list-visible", x)
1174 if "--ignore-masked" in myoptions:
1176 #we are testing deps for a masked package; give it some lee-way
1178 matchmode="match-all"
1181 matchmode="match-visible"
1183 if prof[1] == "dev":
1184 suffix=suffix+"indev"
1186 for mytype,mypos in [["DEPEND",len(missingvars)],["RDEPEND",len(missingvars)+1],["PDEPEND",len(missingvars)+2]]:
1188 mykey=mytype+".bad"+suffix
1189 myvalue = myaux[mytype]
1193 mydep = portage.dep_check(myvalue, portdb,
1194 dep_settings, use="all", mode=matchmode,
1197 stats[mykey]=stats[mykey]+1
1198 fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(e[0]))
1203 #we have some unsolvable deps
1204 #remove ! deps, which always show up as unsatisfiable
1206 while d<len(mydep[1]):
1207 if mydep[1][d][0]=="!":
1211 #if we emptied out our list, continue:
1214 stats[mykey]=stats[mykey]+1
1215 fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(mydep[1]))
1217 stats[mykey]=stats[mykey]+1
1218 fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(mydep[1]))
1220 # this check needs work, it won't catch (\ndie)
1221 if not os.system("egrep '^[^#]*\([^)]*\<die\>' "+checkdir+"/"+y+".ebuild >/dev/null 2>&1"):
1222 stats["ebuild.nesteddie"]=stats["ebuild.nesteddie"]+1
1223 fails["ebuild.nesteddie"].append(x+"/"+y+".ebuild")
1224 # uselist checks - global
1227 for myflag in myaux["IUSE"].split():
1228 if myflag.startswith("+"):
1229 default_use.append(myflag)
1231 myuse.append(myflag)
1232 for mypos in range(len(myuse)-1,-1,-1):
1233 if myuse[mypos] and (myuse[mypos] in uselist):
1235 # uselist checks - local
1236 mykey = portage.dep_getkey(catpkg)
1237 if luselist.has_key(mykey):
1238 for mypos in range(len(myuse)-1,-1,-1):
1239 if myuse[mypos] and (myuse[mypos] in luselist[mykey]):
1241 if default_use and myaux["EAPI"] == "0":
1242 myuse += default_use
1243 for mypos in range(len(myuse)):
1244 stats["IUSE.invalid"]=stats["IUSE.invalid"]+1
1245 fails["IUSE.invalid"].append(x+"/"+y+".ebuild: %s" % myuse[mypos])
1248 if not badlicsyntax:
1249 myuse = myaux["LICENSE"]
1250 # Parse the LICENSE variable, remove USE conditions and
1252 myuse=portage_dep.use_reduce(portage_dep.paren_reduce(myuse), matchall=1)
1253 myuse=portage.flatten(myuse)
1254 # Check each entry to ensure that it exists in PORTDIR's
1255 # license directory.
1256 for mypos in range(0,len(myuse)):
1257 # Need to check for "||" manually as no portage
1258 # function will remove it without removing values.
1259 if myuse[mypos] not in liclist and myuse[mypos] != "||":
1260 stats["LICENSE.invalid"]=stats["LICENSE.invalid"]+1
1261 fails["LICENSE.invalid"].append(x+"/"+y+".ebuild: %s" % myuse[mypos])
1264 myuse = myaux["KEYWORDS"].split()
1272 if myskey not in kwlist:
1273 stats["KEYWORDS.invalid"] += 1
1274 fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s" % mykey)
1275 elif not profiles.has_key(myskey):
1276 stats["KEYWORDS.invalid"] += 1
1277 fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s (profile invalid)" % mykey)
1280 myear = time.gmtime(os.stat(checkdir+"/"+y+".ebuild")[ST_MTIME])[0]
1281 gentoo_copyright = re.compile(r'^# Copyright ((1999|200\d)-)?' + str(myear) + r' Gentoo Foundation')
1282 gentoo_license = re.compile(r'^# Distributed under the terms of the GNU General Public License v2$')
1283 cvs_header = re.compile(r'^#\s*\$Header.*\$$')
1284 ignore_line = re.compile(r'(^$)|(^(\t)*#)')
1285 leading_spaces = re.compile(r'^[\S\t]')
1286 trailing_whitespace = re.compile(r'.*([\S]$)')
1287 readonly_assignment = re.compile(r'^\s*(export\s+)?(A|CATEGORY|P|PV|PN|PR|PVR|PF|D|WORKDIR|FILESDIR|FEATURES|USE)=')
1288 continuation_symbol = re.compile(r'(.*[ ]+[\\][ ].*)')
1289 line_continuation_quoted = re.compile(r'(\"|\')(([\w ,:;#\[\]\.`=/|\$\^\*{}()\'-])|(\\.))*\1')
1290 line_continuation = re.compile(r'([^#]*\S)(\s+|\t)\\$')
1292 previous_line = None
1293 for line in input(checkdir+"/"+y+".ebuild"):
1295 # Gentoo copyright check
1297 match = gentoo_copyright.match(line)
1299 myerrormsg = "Copyright header Error. Possibly date related."
1300 stats["ebuild.badheader"] +=1
1301 fails["ebuild.badheader"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1302 # Gentoo license check
1304 match = gentoo_license.match(line)
1306 myerrormsg = "Gentoo License Error."
1307 stats["ebuild.badheader"] +=1
1308 fails["ebuild.badheader"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1311 match = cvs_header.match(line)
1313 myerrormsg = "CVS Header Error."
1314 stats["ebuild.badheader"] +=1
1315 fails["ebuild.badheader"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1317 match = ignore_line.match(line)
1319 # Excluded Blank lines and full line comments. Good!
1320 # Leading Spaces Check
1321 match = leading_spaces.match(line)
1323 #Line has got leading spaces. Bad!
1324 myerrormsg = "Leading Space Syntax Error. Line %d" % linenum
1325 stats["ebuild.minorsyn"] +=1
1326 fails["ebuild.minorsyn"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1327 # Trailing whitespace check
1328 match = trailing_whitespace.match(line)
1330 #Line has got trailing whitespace. Bad!
1331 myerrormsg = "Trailing whitespace Syntax Error. Line %d" % linenum
1332 stats["ebuild.minorsyn"] +=1
1333 fails["ebuild.minorsyn"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1334 # Readonly variable assignment check
1335 match = readonly_assignment.match(line)
1336 # The regex can give a false positive for continued lines,
1337 # so we check the previous line to see if it was continued.
1338 if match and (not previous_line or not line_continuation.match(previous_line)):
1339 # invalid assignment, very bad!
1340 myerrormsg = "Readonly variable assignment to %s on line %d" % (match.group(2), linenum)
1341 stats["variable.readonly"] += 1
1342 fails["variable.readonly"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1343 # Line continuation check
1344 match = continuation_symbol.match(line)
1346 #Excluded lines not even containing a " \" match. Good!
1347 line = re.sub(line_continuation_quoted,"\"\"",line)
1348 #line has been edited to collapsed "" and '' quotes to "". Good!
1349 match = continuation_symbol.match(line)
1351 #Again exclude lines not even containing a " \" match. Good!
1352 #This repetition is done for a slight performance increase
1353 match = line_continuation.match(line)
1355 #Line has a line continuation error. Bad!
1356 myerrormsg = "Line continuation (\"\\\") Syntax Error. Line %d" % linenum
1357 stats["ebuild.majorsyn"] +=1
1358 fails["ebuild.majorsyn"].append(x+"/"+y+".ebuild: %s" % myerrormsg)
1359 previous_line = line
1362 # Check for 'all unstable' or 'all masked' -- ACCEPT_KEYWORDS is stripped
1363 # XXX -- Needs to be implemented in dep code. Can't determine ~arch nicely.
1364 #if not portage.portdb.xmatch("bestmatch-visible",x):
1365 # stats["ebuild.nostable"]+=1
1366 # fails["ebuild.nostable"].append(x)
1367 if allmasked and repolevel == 3:
1368 stats["ebuild.allmasked"]+=1
1369 fails["ebuild.allmasked"].append(x)
1371 #Pickle and save results for instant reuse in last and lfull
1372 savef=open('/var/cache/edb/repo.stats','w')
1373 pickle.dump(stats,savef)
1375 savef=open('/var/cache/edb/repo.fails','w')
1376 pickle.dump(fails,savef)
1378 if not (os.stat('/var/cache/edb/repo.stats')[ST_GID] == getgrnam('portage')[2]):
1379 os.chown('/var/cache/edb/repo.stats',os.geteuid(),getgrnam('portage')[2])
1380 os.chmod('/var/cache/edb/repo.stats',0664)
1381 if not (os.stat('/var/cache/edb/repo.fails')[ST_GID] == getgrnam('portage')[2]):
1382 os.chown('/var/cache/edb/repo.fails',os.geteuid(),getgrnam('portage')[2])
1383 os.chmod('/var/cache/edb/repo.fails',0664)
1386 #dofail will be set to 1 if we have failed in at least one non-warning category
1388 #dowarn will be set to 1 if we tripped any warnings
1390 #dofull will be set if we should print a "repoman full" informational message
1393 if not isCvs and (string.find(x, "notadded") != -1):
1397 if x not in qawarnings:
1401 print " "+string.ljust(x,30),
1403 print green(`stats[x]`)
1405 elif x in qawarnings:
1406 print yellow(`stats[x]`)
1408 print red(`stats[x]`)
1419 def grouplist(mylist,seperator="/"):
1420 """(list,seperator="/") -- Takes a list of elements; groups them into
1421 same initial element categories. Returns a dict of {base:[sublist]}
1422 From: ["blah/foo","spork/spatula","blah/weee/splat"]
1423 To: {"blah":["foo","weee/splat"], "spork":["spatula"]}"""
1426 xs=string.split(x,seperator)
1429 if xs[0] not in mygroups.keys():
1430 mygroups[xs[0]]=[string.join(xs[1:],seperator)]
1432 mygroups[xs[0]]+=[string.join(xs[1:],seperator)]
1435 if mymode!="commit":
1437 print bold("Note: type \"repoman full\" for a complete listing.")
1440 if dowarn and not dofail:
1442 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.\""
1444 print green("RepoMan sez:"),"\"OK for now, but I'll be back ...\""
1446 print green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\""
1451 print turquoise("Please fix these important QA issues first.")
1452 print green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n"
1455 if "--pretend" in myoptions:
1456 print green("RepoMan sez:"), "\"So, you want to play it safe. Good call.\"\n"
1458 if fails["digest.missing"]:
1459 print green("Creating missing digests...")
1460 for x in fails["digest.missing"]:
1461 xs=string.split(x,"/")
1463 myeb=string.join(xs[:-1],"/")+"/"+xs[-1][7:]
1464 if "--pretend" in myoptions:
1465 print "(ebuild "+portdir+"/"+myeb+".ebuild digest)"
1467 retval=os.system("ebuild "+portdir+"/"+myeb+".ebuild digest")
1469 print "!!! Exiting on ebuild digest (shell) error code:",retval
1472 mycvstree=cvstree.getentries("./",recursive=1)
1473 if isCvs and not mycvstree:
1474 print "!!! It seems we don't have a cvs tree?"
1477 myunadded=cvstree.findunadded(mycvstree,recursive=1,basedir="./")
1480 for x in range(len(myunadded)-1,-1,-1):
1481 xs=string.split(myunadded[x],"/")
1483 print "!!! files dir is not added! Please correct this."
1485 elif xs[-1]=="Manifest":
1486 # It's a manifest... auto add
1487 myautoadd+=[myunadded[x]]
1489 elif len(xs[-1])>=7:
1490 if xs[-1][:7]=="digest-":
1492 myeb=string.join(xs[:-1]+[xs[-1][7:]],"/")+".ebuild"
1493 if os.path.exists(myeb):
1494 # Ebuild exists for digest... So autoadd it.
1495 myautoadd+=[myunadded[x]]
1499 print ">>> Auto-Adding missing digests..."
1500 if "--pretend" in myoptions:
1501 print "(/usr/bin/cvs add "+string.join(myautoadd)+")"
1504 retval=os.system("/usr/bin/cvs add "+string.join(myautoadd))
1506 print "!!! Exiting on cvs (shell) error code:",retval
1510 print red("!!! The following files are in your cvs tree but are not added to the master")
1511 print red("!!! tree. Please remove them from the cvs tree or add them to the master tree.")
1520 print "Performing a "+green("cvs -n up")+" with a little magic grep to check for updates."
1521 retval=getstatusoutput("/usr/bin/cvs -n up 2>&1 | egrep '^[^\?] .*' | egrep -v '^. .*/digest-[^/]+|^cvs server: .* -- ignored$'")
1523 mylines=string.split(retval[1], "\n")
1528 if x[0] not in "UPMAR": # Updates,Patches,Modified,Added,Removed
1529 print red("!!! Please fix the following issues reported from cvs: ")+green("(U,P,M,A,R are ok)")
1530 print red("!!! Note: This is a pretend/no-modify pass...")
1534 elif x[0] in ["U","P"]:
1538 print green("Fetching trivial updates...")
1539 if "--pretend" in myoptions:
1540 print "(/usr/bin/cvs up "+string.join(myupdates)+")"
1543 retval=os.system("/usr/bin/cvs up "+string.join(myupdates))
1545 print "!!! cvs exited with an error. Terminating."
1549 mycvstree=cvstree.getentries("./",recursive=1)
1550 mychanged=cvstree.findchanged(mycvstree,recursive=1,basedir="./")
1551 for manifest in [file for file in mychanged if '/Manifest' in file]:
1552 mychanged.remove(manifest)
1553 mynew=cvstree.findnew(mycvstree,recursive=1,basedir="./")
1554 myremoved=cvstree.findremoved(mycvstree,recursive=1,basedir="./")
1555 if not (mychanged or mynew or myremoved):
1557 print green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\"\n"
1559 print "(Didn't find any changed files...)"
1563 myupdates=mychanged+mynew
1566 headerstring="'\$(Header|Id)"
1567 headerstring+=".*\$'"
1568 for myfile in myupdates:
1569 myout=getstatusoutput("egrep -q "+headerstring+" "+myfile)
1571 myheaders.append(myfile)
1573 print "*",green(str(len(myupdates))),"files being committed...",green(str(len(myheaders))),"have headers that will change."
1574 print "*","Files with headers will cause the manifests to be made and recommited."
1576 print "myupdates:",myupdates
1577 print "myheaders:",myheaders
1580 if commitmessagefile:
1582 f = open(commitmessagefile)
1583 commitmessage = f.read()
1586 except (IOError, OSError), e:
1587 if e.errno == errno.ENOENT:
1588 portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % commitmessagefile)
1591 # We've read the content so the file is no longer needed.
1592 commitmessagefile = None
1593 if not commitmessage:
1594 print "Please enter a CVS commit message at the prompt:"
1595 while not commitmessage:
1597 commitmessage=raw_input(green("> "))
1598 except KeyboardInterrupt:
1601 commitmessage+="\n(Portage version: "+str(portage.VERSION)+")"
1602 except AttributeError:
1603 print "Failed to insert portage version in message!"
1604 commitmessage+="\n(Portage version: Unknown)"
1605 if not commitmessagefile:
1607 commitmessagefile=tempfile.mktemp(".repoman.msg")
1608 if os.path.exists(commitmessagefile):
1609 os.unlink(commitmessagefile)
1610 mymsg=open(commitmessagefile,"w")
1611 mymsg.write(commitmessage)
1615 print green("Using commit message:")
1616 print green("------------------------------------------------------------------------------")
1618 print green("------------------------------------------------------------------------------")
1622 if "--pretend" in myoptions:
1623 print "(/usr/bin/cvs -q commit -F "+ commitmessagefile +" "+ string.join(myupdates," ")+")"
1625 retval=os.system("/usr/bin/cvs -q commit -F "+ commitmessagefile + " " +string.join(myupdates, " "))
1627 print "!!! Exiting on cvs (shell) error code:",retval
1630 # Setup the GPG commands
1631 def gpgsign(filename):
1632 if "PORTAGE_GPG_KEY" not in repoman_settings:
1633 raise portage_exception.MissingParameter("PORTAGE_GPG_KEY is unset!")
1634 if "PORTAGE_GPG_DIR" not in repoman_settings:
1635 if os.environ.has_key("HOME"):
1636 repoman_settings["PORTAGE_GPG_DIR"] = os.path.join(os.environ["HOME"], ".gnupg")
1638 print "Automatically setting PORTAGE_GPG_DIR to",repoman_settings["PORTAGE_GPG_DIR"]
1640 raise portage_exception.MissingParameter("PORTAGE_GPG_DIR is unset!")
1641 gpg_dir = repoman_settings["PORTAGE_GPG_DIR"]
1642 if gpg_dir.startswith("~") and "HOME" in os.environ:
1643 repoman_settings["PORTAGE_GPG_DIR"] = os.path.join(
1644 os.environ["HOME"], gpg_dir[1:].lstrip(os.path.sep))
1645 if not os.access(repoman_settings["PORTAGE_GPG_DIR"], os.X_OK):
1646 raise portage_exception.InvalidLocation(
1647 "Unable to access directory: PORTAGE_GPG_DIR='%s'" % \
1648 repoman_settings["PORTAGE_GPG_DIR"])
1649 gpgcmd = "gpg --sign --clearsign --yes "
1650 gpgcmd+= "--default-key "+repoman_settings["PORTAGE_GPG_KEY"]
1651 if repoman_settings.has_key("PORTAGE_GPG_DIR"):
1652 gpgcmd += " --homedir "+repoman_settings["PORTAGE_GPG_DIR"]
1653 if "--pretend" in myoptions:
1654 print "("+gpgcmd+" "+filename+")"
1656 rValue = os.system(gpgcmd+" "+filename)
1657 if rValue == os.EX_OK:
1658 os.rename(filename+".asc", filename)
1660 raise portage_exception.PortageException("!!! gpg exited with '" + str(rValue) + "' status")
1663 if myheaders or myupdates or myremoved or mynew:
1664 myfiles=myheaders+myupdates+myremoved+mynew
1665 for x in range(len(myfiles)-1, -1, -1):
1666 if myfiles[x].count("/") < 4-repolevel:
1669 if repolevel==3: # In a package dir
1670 repoman_settings["O"]=os.getcwd()
1671 portage.digestgen([], repoman_settings, manifestonly=1,
1673 elif repolevel==2: # In a category dir
1675 xs=string.split(x,"/")
1680 mydone.append(xs[0])
1681 repoman_settings["O"]=os.path.join(os.getcwd(), xs[0])
1682 portage.digestgen([], repoman_settings, manifestonly=1,
1684 elif repolevel==1: # repo-cvsroot
1685 print green("RepoMan sez:"), "\"You're rather crazy... doing the entire repository.\"\n"
1687 xs=string.split(x,"/")
1690 if string.join(xs[:2],"/") in mydone:
1692 mydone.append(string.join(xs[:2],"/"))
1693 repoman_settings["O"]=os.path.join(os.getcwd(), x[0], x[1])
1694 portage.digestgen([], repoman_settings, manifestonly=1,
1697 print red("I'm confused... I don't know where I am!")
1700 # Force an unsigned commit when more than one Manifest needs to be signed.
1701 if repolevel < 3 and "sign" in repoman_settings.features:
1702 if "--pretend" in myoptions:
1703 print "(/usr/bin/cvs -q commit -F "+commitmessagefile+")"
1705 mymsg=open(commitmessagefile,"w")
1706 mymsg.write(commitmessage)
1707 mymsg.write("\n (Unsigned Manifest commit)")
1709 retval=os.system("/usr/bin/cvs -q commit -F "+commitmessagefile)
1711 print "!!! Exiting on cvs (shell) error code:",retval
1717 if "sign" in repoman_settings.features:
1720 if repolevel==3: # In a package dir
1721 repoman_settings["O"] = "."
1722 gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
1723 elif repolevel==2: # In a category dir
1726 xs=string.split(x,"/")
1731 mydone.append(xs[0])
1732 repoman_settings["O"] = os.path.join(".", xs[0])
1733 gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
1734 elif repolevel==1: # repo-cvsroot
1735 print green("RepoMan sez:"), "\"You're rather crazy... doing the entire repository.\"\n"
1738 xs=string.split(x,"/")
1741 if string.join(xs[:2],"/") in mydone:
1743 mydone.append(string.join(xs[:2],"/"))
1744 repoman_settings["O"] = os.path.join(".", xs[0], xs[1])
1745 gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
1746 except portage_exception.PortageException, e:
1747 portage.writemsg("!!! %s\n" % str(e))
1748 portage.writemsg("!!! Disabled FEATURES='sign'\n")
1751 if need_commit or signed:
1752 if "--pretend" in myoptions:
1753 print "(/usr/bin/cvs -q commit -F "+commitmessagefile+")"
1755 mymsg=open(commitmessagefile,"w")
1756 mymsg.write(commitmessage)
1758 mymsg.write("\n (Signed Manifest commit)")
1760 mymsg.write("\n (Unsigned Manifest commit)")
1762 retval=os.system("/usr/bin/cvs -q commit -F "+commitmessagefile)
1764 print "!!! Exiting on cvs (shell) error code:",retval
1768 os.unlink(commitmessagefile)
1771 print "CVS commit complete."
1773 print "repoman was too scared by not seeing any familiar cvs file that he forgot to commit anything"
1774 print green("RepoMan sez:"), "\"If everyone were like you, I'd be out of business!\"\n"