1*8bcea973SRichard Lowe#!/usr/bin/python2.4 2*8bcea973SRichard Lowe# 3*8bcea973SRichard Lowe# This program is free software; you can redistribute it and/or modify 4*8bcea973SRichard Lowe# it under the terms of the GNU General Public License version 2 5*8bcea973SRichard Lowe# as published by the Free Software Foundation. 6*8bcea973SRichard Lowe# 7*8bcea973SRichard Lowe# This program is distributed in the hope that it will be useful, 8*8bcea973SRichard Lowe# but WITHOUT ANY WARRANTY; without even the implied warranty of 9*8bcea973SRichard Lowe# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10*8bcea973SRichard Lowe# GNU General Public License for more details. 11*8bcea973SRichard Lowe# 12*8bcea973SRichard Lowe# You should have received a copy of the GNU General Public License 13*8bcea973SRichard Lowe# along with this program; if not, write to the Free Software 14*8bcea973SRichard Lowe# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 15*8bcea973SRichard Lowe# 16*8bcea973SRichard Lowe 17*8bcea973SRichard Lowe# 18*8bcea973SRichard Lowe# Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. 19*8bcea973SRichard Lowe# Copyright 2008, 2012 Richard Lowe 20*8bcea973SRichard Lowe# 21*8bcea973SRichard Lowe 22*8bcea973SRichard Loweimport getopt 23*8bcea973SRichard Loweimport os 24*8bcea973SRichard Loweimport re 25*8bcea973SRichard Loweimport subprocess 26*8bcea973SRichard Loweimport sys 27*8bcea973SRichard Lowe 28*8bcea973SRichard Lowefrom cStringIO import StringIO 29*8bcea973SRichard Lowe 30*8bcea973SRichard Lowe# This is necessary because, in a fit of pique, we used hg-format ignore lists 31*8bcea973SRichard Lowe# for NOT files. 32*8bcea973SRichard Lowefrom mercurial import ignore 33*8bcea973SRichard Lowe 34*8bcea973SRichard Lowe# 35*8bcea973SRichard Lowe# Adjust the load path based on our location and the version of python into 36*8bcea973SRichard Lowe# which it is being loaded. This assumes the normal onbld directory 37*8bcea973SRichard Lowe# structure, where we are in bin/ and the modules are in 38*8bcea973SRichard Lowe# lib/python(version)?/onbld/Scm/. If that changes so too must this. 39*8bcea973SRichard Lowe# 40*8bcea973SRichard Lowesys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "lib", 41*8bcea973SRichard Lowe "python%d.%d" % sys.version_info[:2])) 42*8bcea973SRichard Lowe 43*8bcea973SRichard Lowe# 44*8bcea973SRichard Lowe# Add the relative path to usr/src/tools to the load path, such that when run 45*8bcea973SRichard Lowe# from the source tree we use the modules also within the source tree. 46*8bcea973SRichard Lowe# 47*8bcea973SRichard Lowesys.path.insert(2, os.path.join(os.path.dirname(__file__), "..")) 48*8bcea973SRichard Lowe 49*8bcea973SRichard Lowefrom onbld.Checks import Comments, Copyright, CStyle, HdrChk 50*8bcea973SRichard Lowefrom onbld.Checks import JStyle, Keywords, Mapfile 51*8bcea973SRichard Lowe 52*8bcea973SRichard Lowe 53*8bcea973SRichard Loweclass GitError(Exception): 54*8bcea973SRichard Lowe pass 55*8bcea973SRichard Lowe 56*8bcea973SRichard Lowedef git(command): 57*8bcea973SRichard Lowe """Run a command and return a stream containing its stdout (and write its 58*8bcea973SRichard Lowe stderr to its stdout)""" 59*8bcea973SRichard Lowe 60*8bcea973SRichard Lowe if type(command) != list: 61*8bcea973SRichard Lowe command = command.split() 62*8bcea973SRichard Lowe 63*8bcea973SRichard Lowe command = ["git"] + command 64*8bcea973SRichard Lowe 65*8bcea973SRichard Lowe p = subprocess.Popen(command, 66*8bcea973SRichard Lowe stdout=subprocess.PIPE, 67*8bcea973SRichard Lowe stderr=subprocess.STDOUT) 68*8bcea973SRichard Lowe 69*8bcea973SRichard Lowe err = p.wait() 70*8bcea973SRichard Lowe if err != 0: 71*8bcea973SRichard Lowe raise GitError(p.stdout.read()) 72*8bcea973SRichard Lowe 73*8bcea973SRichard Lowe return p.stdout 74*8bcea973SRichard Lowe 75*8bcea973SRichard Lowe 76*8bcea973SRichard Lowedef git_root(): 77*8bcea973SRichard Lowe """Return the root of the current git workspace""" 78*8bcea973SRichard Lowe 79*8bcea973SRichard Lowe p = git('rev-parse --git-dir') 80*8bcea973SRichard Lowe 81*8bcea973SRichard Lowe if not p: 82*8bcea973SRichard Lowe sys.stderr.write("Failed finding git workspace\n") 83*8bcea973SRichard Lowe sys.exit(err) 84*8bcea973SRichard Lowe 85*8bcea973SRichard Lowe return os.path.abspath(os.path.join(p.readlines()[0], 86*8bcea973SRichard Lowe os.path.pardir)) 87*8bcea973SRichard Lowe 88*8bcea973SRichard Lowe 89*8bcea973SRichard Lowedef git_branch(): 90*8bcea973SRichard Lowe """Return the current git branch""" 91*8bcea973SRichard Lowe 92*8bcea973SRichard Lowe p = git('branch') 93*8bcea973SRichard Lowe 94*8bcea973SRichard Lowe if not p: 95*8bcea973SRichard Lowe sys.stderr.write("Failed finding git branch\n") 96*8bcea973SRichard Lowe sys.exit(err) 97*8bcea973SRichard Lowe 98*8bcea973SRichard Lowe for elt in p: 99*8bcea973SRichard Lowe if elt[0] == '*': 100*8bcea973SRichard Lowe if elt.endswith('(no branch)'): 101*8bcea973SRichard Lowe return None 102*8bcea973SRichard Lowe return elt.split()[1] 103*8bcea973SRichard Lowe 104*8bcea973SRichard Lowe 105*8bcea973SRichard Lowedef git_parent_branch(branch): 106*8bcea973SRichard Lowe """Return the parent of the current git branch. 107*8bcea973SRichard Lowe 108*8bcea973SRichard Lowe If this branch tracks a remote branch, return the remote branch which is 109*8bcea973SRichard Lowe tracked. If not, default to origin/master.""" 110*8bcea973SRichard Lowe 111*8bcea973SRichard Lowe if not branch: 112*8bcea973SRichard Lowe return None 113*8bcea973SRichard Lowe 114*8bcea973SRichard Lowe p = git("for-each-ref --format=%(refname:short) %(upstream:short) " + 115*8bcea973SRichard Lowe "refs/heads/") 116*8bcea973SRichard Lowe 117*8bcea973SRichard Lowe if not p: 118*8bcea973SRichard Lowe sys.stderr.write("Failed finding git parent branch\n") 119*8bcea973SRichard Lowe sys.exit(err) 120*8bcea973SRichard Lowe 121*8bcea973SRichard Lowe for line in p: 122*8bcea973SRichard Lowe # Git 1.7 will leave a ' ' trailing any non-tracking branch 123*8bcea973SRichard Lowe if ' ' in line and not line.endswith(' \n'): 124*8bcea973SRichard Lowe local, remote = line.split() 125*8bcea973SRichard Lowe if local == branch: 126*8bcea973SRichard Lowe return remote 127*8bcea973SRichard Lowe return 'origin/master' 128*8bcea973SRichard Lowe 129*8bcea973SRichard Lowe 130*8bcea973SRichard Lowedef git_comments(parent): 131*8bcea973SRichard Lowe """Return a list of any checkin comments on this git branch""" 132*8bcea973SRichard Lowe 133*8bcea973SRichard Lowe p = git('log --pretty=format:%%B %s..' % parent) 134*8bcea973SRichard Lowe 135*8bcea973SRichard Lowe if not p: 136*8bcea973SRichard Lowe sys.stderr.write("Failed getting git comments\n") 137*8bcea973SRichard Lowe sys.exit(err) 138*8bcea973SRichard Lowe 139*8bcea973SRichard Lowe return map(lambda x: x.strip(), p.readlines()) 140*8bcea973SRichard Lowe 141*8bcea973SRichard Lowe 142*8bcea973SRichard Lowedef git_file_list(parent, paths=None): 143*8bcea973SRichard Lowe """Return the set of files which have ever changed on this branch. 144*8bcea973SRichard Lowe 145*8bcea973SRichard Lowe NB: This includes files which no longer exist, or no longer actually 146*8bcea973SRichard Lowe differ.""" 147*8bcea973SRichard Lowe 148*8bcea973SRichard Lowe p = git("log --name-only --pretty=format: %s.. %s" % 149*8bcea973SRichard Lowe (parent, ' '.join(paths))) 150*8bcea973SRichard Lowe 151*8bcea973SRichard Lowe if not p: 152*8bcea973SRichard Lowe sys.stderr.write("Failed building file-list from git\n") 153*8bcea973SRichard Lowe sys.exit(err) 154*8bcea973SRichard Lowe 155*8bcea973SRichard Lowe ret = set() 156*8bcea973SRichard Lowe for fname in p: 157*8bcea973SRichard Lowe if fname and not fname.isspace() and fname not in ret: 158*8bcea973SRichard Lowe ret.add(fname.strip()) 159*8bcea973SRichard Lowe 160*8bcea973SRichard Lowe return ret 161*8bcea973SRichard Lowe 162*8bcea973SRichard Lowe 163*8bcea973SRichard Lowedef not_check(root, cmd): 164*8bcea973SRichard Lowe """Return a function which returns True if a file given as an argument 165*8bcea973SRichard Lowe should be excluded from the check named by 'cmd'""" 166*8bcea973SRichard Lowe 167*8bcea973SRichard Lowe ignorefiles = filter(os.path.exists, 168*8bcea973SRichard Lowe [os.path.join(root, ".git", "%s.NOT" % cmd), 169*8bcea973SRichard Lowe os.path.join(root, "exception_lists", cmd)]) 170*8bcea973SRichard Lowe if len(ignorefiles) > 0: 171*8bcea973SRichard Lowe return ignore.ignore(root, ignorefiles, sys.stderr.write) 172*8bcea973SRichard Lowe else: 173*8bcea973SRichard Lowe return lambda x: False 174*8bcea973SRichard Lowe 175*8bcea973SRichard Lowe 176*8bcea973SRichard Lowedef gen_files(root, parent, paths, exclude): 177*8bcea973SRichard Lowe """Return a function producing file names, relative to the current 178*8bcea973SRichard Lowe directory, of any file changed on this branch (limited to 'paths' if 179*8bcea973SRichard Lowe requested), and excluding files for which exclude returns a true value """ 180*8bcea973SRichard Lowe 181*8bcea973SRichard Lowe # Taken entirely from Python 2.6's os.path.relpath which we would use if we 182*8bcea973SRichard Lowe # could. 183*8bcea973SRichard Lowe def relpath(path, here): 184*8bcea973SRichard Lowe c = os.path.abspath(os.path.join(root, path)).split(os.path.sep) 185*8bcea973SRichard Lowe s = os.path.abspath(here).split(os.path.sep) 186*8bcea973SRichard Lowe l = len(os.path.commonprefix((s, c))) 187*8bcea973SRichard Lowe return os.path.join(*[os.path.pardir] * (len(s)-l) + c[l:]) 188*8bcea973SRichard Lowe 189*8bcea973SRichard Lowe def ret(select=None): 190*8bcea973SRichard Lowe if not select: 191*8bcea973SRichard Lowe select = lambda x: True 192*8bcea973SRichard Lowe 193*8bcea973SRichard Lowe for f in git_file_list(parent, paths): 194*8bcea973SRichard Lowe f = relpath(f, '.') 195*8bcea973SRichard Lowe if (os.path.exists(f) and select(f) and not exclude(f)): 196*8bcea973SRichard Lowe yield f 197*8bcea973SRichard Lowe return ret 198*8bcea973SRichard Lowe 199*8bcea973SRichard Lowe 200*8bcea973SRichard Lowedef comchk(root, parent, flist, output): 201*8bcea973SRichard Lowe output.write("Comments:\n") 202*8bcea973SRichard Lowe 203*8bcea973SRichard Lowe return Comments.comchk(git_comments(parent), check_db=True, 204*8bcea973SRichard Lowe output=output) 205*8bcea973SRichard Lowe 206*8bcea973SRichard Lowe 207*8bcea973SRichard Lowedef mapfilechk(root, parent, flist, output): 208*8bcea973SRichard Lowe ret = 0 209*8bcea973SRichard Lowe 210*8bcea973SRichard Lowe # We are interested in examining any file that has the following 211*8bcea973SRichard Lowe # in its final path segment: 212*8bcea973SRichard Lowe # - Contains the word 'mapfile' 213*8bcea973SRichard Lowe # - Begins with 'map.' 214*8bcea973SRichard Lowe # - Ends with '.map' 215*8bcea973SRichard Lowe # We don't want to match unless these things occur in final path segment 216*8bcea973SRichard Lowe # because directory names with these strings don't indicate a mapfile. 217*8bcea973SRichard Lowe # We also ignore files with suffixes that tell us that the files 218*8bcea973SRichard Lowe # are not mapfiles. 219*8bcea973SRichard Lowe MapfileRE = re.compile(r'.*((mapfile[^/]*)|(/map\.+[^/]*)|(\.map))$', 220*8bcea973SRichard Lowe re.IGNORECASE) 221*8bcea973SRichard Lowe NotMapSuffixRE = re.compile(r'.*\.[ch]$', re.IGNORECASE) 222*8bcea973SRichard Lowe 223*8bcea973SRichard Lowe output.write("Mapfile comments:\n") 224*8bcea973SRichard Lowe 225*8bcea973SRichard Lowe for f in flist(lambda x: MapfileRE.match(x) and not 226*8bcea973SRichard Lowe NotMapSuffixRE.match(x)): 227*8bcea973SRichard Lowe fh = open(f, 'r') 228*8bcea973SRichard Lowe ret |= Mapfile.mapfilechk(fh, output=output) 229*8bcea973SRichard Lowe fh.close() 230*8bcea973SRichard Lowe return ret 231*8bcea973SRichard Lowe 232*8bcea973SRichard Lowe 233*8bcea973SRichard Lowedef copyright(root, parent, flist, output): 234*8bcea973SRichard Lowe ret = 0 235*8bcea973SRichard Lowe output.write("Copyrights:\n") 236*8bcea973SRichard Lowe for f in flist(): 237*8bcea973SRichard Lowe fh = open(f, 'r') 238*8bcea973SRichard Lowe ret |= Copyright.copyright(fh, output=output) 239*8bcea973SRichard Lowe fh.close() 240*8bcea973SRichard Lowe return ret 241*8bcea973SRichard Lowe 242*8bcea973SRichard Lowe 243*8bcea973SRichard Lowedef hdrchk(root, parent, flist, output): 244*8bcea973SRichard Lowe ret = 0 245*8bcea973SRichard Lowe output.write("Header format:\n") 246*8bcea973SRichard Lowe for f in flist(lambda x: x.endswith('.h')): 247*8bcea973SRichard Lowe fh = open(f, 'r') 248*8bcea973SRichard Lowe ret |= HdrChk.hdrchk(fh, lenient=True, output=output) 249*8bcea973SRichard Lowe fh.close() 250*8bcea973SRichard Lowe return ret 251*8bcea973SRichard Lowe 252*8bcea973SRichard Lowe 253*8bcea973SRichard Lowedef cstyle(root, parent, flist, output): 254*8bcea973SRichard Lowe ret = 0 255*8bcea973SRichard Lowe output.write("C style:\n") 256*8bcea973SRichard Lowe for f in flist(lambda x: x.endswith('.c') or x.endswith('.h')): 257*8bcea973SRichard Lowe fh = open(f, 'r') 258*8bcea973SRichard Lowe ret |= CStyle.cstyle(fh, output=output, picky=True, 259*8bcea973SRichard Lowe check_posix_types=True, 260*8bcea973SRichard Lowe check_continuation=True) 261*8bcea973SRichard Lowe fh.close() 262*8bcea973SRichard Lowe return ret 263*8bcea973SRichard Lowe 264*8bcea973SRichard Lowe 265*8bcea973SRichard Lowedef jstyle(root, parent, flist, output): 266*8bcea973SRichard Lowe ret = 0 267*8bcea973SRichard Lowe output.write("Java style:\n") 268*8bcea973SRichard Lowe for f in flist(lambda x: x.endswith('.java')): 269*8bcea973SRichard Lowe fh = open(f, 'r') 270*8bcea973SRichard Lowe ret |= JStyle.jstyle(fh, output=output, picky=True) 271*8bcea973SRichard Lowe fh.close() 272*8bcea973SRichard Lowe return ret 273*8bcea973SRichard Lowe 274*8bcea973SRichard Lowe 275*8bcea973SRichard Lowedef keywords(root, parent, flist, output): 276*8bcea973SRichard Lowe ret = 0 277*8bcea973SRichard Lowe output.write("SCCS Keywords:\n") 278*8bcea973SRichard Lowe for f in flist(): 279*8bcea973SRichard Lowe fh = open(f, 'r') 280*8bcea973SRichard Lowe ret |= Keywords.keywords(fh, output=output) 281*8bcea973SRichard Lowe fh.close() 282*8bcea973SRichard Lowe return ret 283*8bcea973SRichard Lowe 284*8bcea973SRichard Lowe 285*8bcea973SRichard Lowedef run_checks(root, parent, cmds, paths='', opts={}): 286*8bcea973SRichard Lowe """Run the checks given in 'cmds', expected to have well-known signatures, 287*8bcea973SRichard Lowe and report results for any which fail. 288*8bcea973SRichard Lowe 289*8bcea973SRichard Lowe Return failure if any of them did. 290*8bcea973SRichard Lowe 291*8bcea973SRichard Lowe NB: the function name of the commands passed in is used to name the NOT 292*8bcea973SRichard Lowe file which excepts files from them.""" 293*8bcea973SRichard Lowe 294*8bcea973SRichard Lowe ret = 0 295*8bcea973SRichard Lowe 296*8bcea973SRichard Lowe for cmd in cmds: 297*8bcea973SRichard Lowe s = StringIO() 298*8bcea973SRichard Lowe 299*8bcea973SRichard Lowe exclude = not_check(root, cmd.func_name) 300*8bcea973SRichard Lowe result = cmd(root, parent, gen_files(root, parent, paths, exclude), 301*8bcea973SRichard Lowe output=s) 302*8bcea973SRichard Lowe ret |= result 303*8bcea973SRichard Lowe 304*8bcea973SRichard Lowe if result != 0: 305*8bcea973SRichard Lowe print s.getvalue() 306*8bcea973SRichard Lowe 307*8bcea973SRichard Lowe return ret 308*8bcea973SRichard Lowe 309*8bcea973SRichard Lowe 310*8bcea973SRichard Lowedef nits(root, parent, paths): 311*8bcea973SRichard Lowe cmds = [copyright, 312*8bcea973SRichard Lowe cstyle, 313*8bcea973SRichard Lowe hdrchk, 314*8bcea973SRichard Lowe jstyle, 315*8bcea973SRichard Lowe keywords, 316*8bcea973SRichard Lowe mapfilechk] 317*8bcea973SRichard Lowe run_checks(root, parent, cmds, paths) 318*8bcea973SRichard Lowe 319*8bcea973SRichard Lowe 320*8bcea973SRichard Lowedef pbchk(root, parent, paths): 321*8bcea973SRichard Lowe cmds = [comchk, 322*8bcea973SRichard Lowe copyright, 323*8bcea973SRichard Lowe cstyle, 324*8bcea973SRichard Lowe hdrchk, 325*8bcea973SRichard Lowe jstyle, 326*8bcea973SRichard Lowe keywords, 327*8bcea973SRichard Lowe mapfilechk] 328*8bcea973SRichard Lowe run_checks(root, parent, cmds) 329*8bcea973SRichard Lowe 330*8bcea973SRichard Lowe 331*8bcea973SRichard Lowedef main(cmd, args): 332*8bcea973SRichard Lowe parent_branch = None 333*8bcea973SRichard Lowe 334*8bcea973SRichard Lowe try: 335*8bcea973SRichard Lowe opts, args = getopt.getopt(args, 'b:') 336*8bcea973SRichard Lowe except getopt.GetoptError, e: 337*8bcea973SRichard Lowe sys.stderr.write(str(e) + '\n') 338*8bcea973SRichard Lowe sys.stderr.write("Usage: %s [-b branch] [path...]\n" % cmd) 339*8bcea973SRichard Lowe sys.exit(1) 340*8bcea973SRichard Lowe 341*8bcea973SRichard Lowe for opt, arg in opts: 342*8bcea973SRichard Lowe if opt == '-b': 343*8bcea973SRichard Lowe parent_branch = arg 344*8bcea973SRichard Lowe 345*8bcea973SRichard Lowe if not parent_branch: 346*8bcea973SRichard Lowe parent_branch = git_parent_branch(git_branch()) 347*8bcea973SRichard Lowe 348*8bcea973SRichard Lowe func = nits 349*8bcea973SRichard Lowe if cmd == 'git-pbchk': 350*8bcea973SRichard Lowe func = pbchk 351*8bcea973SRichard Lowe if args: 352*8bcea973SRichard Lowe sys.stderr.write("only complete workspaces may be pbchk'd\n"); 353*8bcea973SRichard Lowe sys.exit(1) 354*8bcea973SRichard Lowe 355*8bcea973SRichard Lowe func(git_root(), parent_branch, args) 356*8bcea973SRichard Lowe 357*8bcea973SRichard Loweif __name__ == '__main__': 358*8bcea973SRichard Lowe try: 359*8bcea973SRichard Lowe main(os.path.basename(sys.argv[0]), sys.argv[1:]) 360*8bcea973SRichard Lowe except GitError, e: 361*8bcea973SRichard Lowe sys.stderr.write("failed to run git:\n %s\n" % str(e)) 362*8bcea973SRichard Lowe sys.exit(1) 363