16066d16eSPiotr Jasiukajtis#!/usr/bin/python2.6 28bcea973SRichard Lowe# 38bcea973SRichard Lowe# This program is free software; you can redistribute it and/or modify 48bcea973SRichard Lowe# it under the terms of the GNU General Public License version 2 58bcea973SRichard Lowe# as published by the Free Software Foundation. 68bcea973SRichard Lowe# 78bcea973SRichard Lowe# This program is distributed in the hope that it will be useful, 88bcea973SRichard Lowe# but WITHOUT ANY WARRANTY; without even the implied warranty of 98bcea973SRichard Lowe# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 108bcea973SRichard Lowe# GNU General Public License for more details. 118bcea973SRichard Lowe# 128bcea973SRichard Lowe# You should have received a copy of the GNU General Public License 138bcea973SRichard Lowe# along with this program; if not, write to the Free Software 148bcea973SRichard Lowe# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 158bcea973SRichard Lowe# 168bcea973SRichard Lowe 178bcea973SRichard Lowe# 188bcea973SRichard Lowe# Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. 198bcea973SRichard Lowe# Copyright 2008, 2012 Richard Lowe 20*95c635efSGarrett D'Amore# Copyright 2014 Garrett D'Amore <garrett@damore.org> 218bcea973SRichard Lowe# 228bcea973SRichard Lowe 238bcea973SRichard Loweimport getopt 248bcea973SRichard Loweimport os 258bcea973SRichard Loweimport re 268bcea973SRichard Loweimport subprocess 278bcea973SRichard Loweimport sys 28ff50e8e5SRichard Loweimport tempfile 298bcea973SRichard Lowe 308bcea973SRichard Lowefrom cStringIO import StringIO 318bcea973SRichard Lowe 328bcea973SRichard Lowe# This is necessary because, in a fit of pique, we used hg-format ignore lists 338bcea973SRichard Lowe# for NOT files. 348bcea973SRichard Lowefrom mercurial import ignore 358bcea973SRichard Lowe 368bcea973SRichard Lowe# 378bcea973SRichard Lowe# Adjust the load path based on our location and the version of python into 388bcea973SRichard Lowe# which it is being loaded. This assumes the normal onbld directory 398bcea973SRichard Lowe# structure, where we are in bin/ and the modules are in 408bcea973SRichard Lowe# lib/python(version)?/onbld/Scm/. If that changes so too must this. 418bcea973SRichard Lowe# 428bcea973SRichard Lowesys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "lib", 438bcea973SRichard Lowe "python%d.%d" % sys.version_info[:2])) 448bcea973SRichard Lowe 458bcea973SRichard Lowe# 468bcea973SRichard Lowe# Add the relative path to usr/src/tools to the load path, such that when run 478bcea973SRichard Lowe# from the source tree we use the modules also within the source tree. 488bcea973SRichard Lowe# 498bcea973SRichard Lowesys.path.insert(2, os.path.join(os.path.dirname(__file__), "..")) 508bcea973SRichard Lowe 518bcea973SRichard Lowefrom onbld.Checks import Comments, Copyright, CStyle, HdrChk 52*95c635efSGarrett D'Amorefrom onbld.Checks import JStyle, Keywords, ManLint, Mapfile 538bcea973SRichard Lowe 548bcea973SRichard Lowe 558bcea973SRichard Loweclass GitError(Exception): 568bcea973SRichard Lowe pass 578bcea973SRichard Lowe 588bcea973SRichard Lowedef git(command): 598bcea973SRichard Lowe """Run a command and return a stream containing its stdout (and write its 608bcea973SRichard Lowe stderr to its stdout)""" 618bcea973SRichard Lowe 628bcea973SRichard Lowe if type(command) != list: 638bcea973SRichard Lowe command = command.split() 648bcea973SRichard Lowe 658bcea973SRichard Lowe command = ["git"] + command 668bcea973SRichard Lowe 67ff50e8e5SRichard Lowe try: 68ff50e8e5SRichard Lowe tmpfile = tempfile.TemporaryFile(prefix="git-nits") 69ff50e8e5SRichard Lowe except EnvironmentError, e: 70ff50e8e5SRichard Lowe raise GitError("Could not create temporary file: %s\n" % e) 71ff50e8e5SRichard Lowe 72ff50e8e5SRichard Lowe try: 738bcea973SRichard Lowe p = subprocess.Popen(command, 74ff50e8e5SRichard Lowe stdout=tmpfile, 758bcea973SRichard Lowe stderr=subprocess.STDOUT) 76ff50e8e5SRichard Lowe except OSError, e: 77ff50e8e5SRichard Lowe raise GitError("could not execute %s: %s\n" (command, e)) 788bcea973SRichard Lowe 798bcea973SRichard Lowe err = p.wait() 808bcea973SRichard Lowe if err != 0: 818bcea973SRichard Lowe raise GitError(p.stdout.read()) 828bcea973SRichard Lowe 83ff50e8e5SRichard Lowe tmpfile.seek(0) 84ff50e8e5SRichard Lowe return tmpfile 858bcea973SRichard Lowe 868bcea973SRichard Lowe 878bcea973SRichard Lowedef git_root(): 888bcea973SRichard Lowe """Return the root of the current git workspace""" 898bcea973SRichard Lowe 908bcea973SRichard Lowe p = git('rev-parse --git-dir') 918bcea973SRichard Lowe 928bcea973SRichard Lowe if not p: 938bcea973SRichard Lowe sys.stderr.write("Failed finding git workspace\n") 948bcea973SRichard Lowe sys.exit(err) 958bcea973SRichard Lowe 968bcea973SRichard Lowe return os.path.abspath(os.path.join(p.readlines()[0], 978bcea973SRichard Lowe os.path.pardir)) 988bcea973SRichard Lowe 998bcea973SRichard Lowe 1008bcea973SRichard Lowedef git_branch(): 1018bcea973SRichard Lowe """Return the current git branch""" 1028bcea973SRichard Lowe 1038bcea973SRichard Lowe p = git('branch') 1048bcea973SRichard Lowe 1058bcea973SRichard Lowe if not p: 1068bcea973SRichard Lowe sys.stderr.write("Failed finding git branch\n") 1078bcea973SRichard Lowe sys.exit(err) 1088bcea973SRichard Lowe 1098bcea973SRichard Lowe for elt in p: 1108bcea973SRichard Lowe if elt[0] == '*': 1118bcea973SRichard Lowe if elt.endswith('(no branch)'): 1128bcea973SRichard Lowe return None 1138bcea973SRichard Lowe return elt.split()[1] 1148bcea973SRichard Lowe 1158bcea973SRichard Lowe 1168bcea973SRichard Lowedef git_parent_branch(branch): 1178bcea973SRichard Lowe """Return the parent of the current git branch. 1188bcea973SRichard Lowe 1198bcea973SRichard Lowe If this branch tracks a remote branch, return the remote branch which is 1208bcea973SRichard Lowe tracked. If not, default to origin/master.""" 1218bcea973SRichard Lowe 1228bcea973SRichard Lowe if not branch: 1238bcea973SRichard Lowe return None 1248bcea973SRichard Lowe 1258bcea973SRichard Lowe p = git("for-each-ref --format=%(refname:short) %(upstream:short) " + 1268bcea973SRichard Lowe "refs/heads/") 1278bcea973SRichard Lowe 1288bcea973SRichard Lowe if not p: 1298bcea973SRichard Lowe sys.stderr.write("Failed finding git parent branch\n") 1308bcea973SRichard Lowe sys.exit(err) 1318bcea973SRichard Lowe 1328bcea973SRichard Lowe for line in p: 1338bcea973SRichard Lowe # Git 1.7 will leave a ' ' trailing any non-tracking branch 1348bcea973SRichard Lowe if ' ' in line and not line.endswith(' \n'): 1358bcea973SRichard Lowe local, remote = line.split() 1368bcea973SRichard Lowe if local == branch: 1378bcea973SRichard Lowe return remote 1388bcea973SRichard Lowe return 'origin/master' 1398bcea973SRichard Lowe 1408bcea973SRichard Lowe 1418bcea973SRichard Lowedef git_comments(parent): 1428bcea973SRichard Lowe """Return a list of any checkin comments on this git branch""" 1438bcea973SRichard Lowe 14427495383SRichard Lowe p = git('log --pretty=tformat:%%B:SEP: %s..' % parent) 1458bcea973SRichard Lowe 1468bcea973SRichard Lowe if not p: 1478bcea973SRichard Lowe sys.stderr.write("Failed getting git comments\n") 1488bcea973SRichard Lowe sys.exit(err) 1498bcea973SRichard Lowe 15027495383SRichard Lowe return [x.strip() for x in p.readlines() if x != ':SEP:\n'] 1518bcea973SRichard Lowe 1528bcea973SRichard Lowe 1538bcea973SRichard Lowedef git_file_list(parent, paths=None): 1548bcea973SRichard Lowe """Return the set of files which have ever changed on this branch. 1558bcea973SRichard Lowe 1568bcea973SRichard Lowe NB: This includes files which no longer exist, or no longer actually 1578bcea973SRichard Lowe differ.""" 1588bcea973SRichard Lowe 1598bcea973SRichard Lowe p = git("log --name-only --pretty=format: %s.. %s" % 1608bcea973SRichard Lowe (parent, ' '.join(paths))) 1618bcea973SRichard Lowe 1628bcea973SRichard Lowe if not p: 1638bcea973SRichard Lowe sys.stderr.write("Failed building file-list from git\n") 1648bcea973SRichard Lowe sys.exit(err) 1658bcea973SRichard Lowe 1668bcea973SRichard Lowe ret = set() 1678bcea973SRichard Lowe for fname in p: 1688bcea973SRichard Lowe if fname and not fname.isspace() and fname not in ret: 1698bcea973SRichard Lowe ret.add(fname.strip()) 1708bcea973SRichard Lowe 1718bcea973SRichard Lowe return ret 1728bcea973SRichard Lowe 1738bcea973SRichard Lowe 1748bcea973SRichard Lowedef not_check(root, cmd): 1758bcea973SRichard Lowe """Return a function which returns True if a file given as an argument 1768bcea973SRichard Lowe should be excluded from the check named by 'cmd'""" 1778bcea973SRichard Lowe 1788bcea973SRichard Lowe ignorefiles = filter(os.path.exists, 1798bcea973SRichard Lowe [os.path.join(root, ".git", "%s.NOT" % cmd), 1808bcea973SRichard Lowe os.path.join(root, "exception_lists", cmd)]) 1818bcea973SRichard Lowe if len(ignorefiles) > 0: 1828bcea973SRichard Lowe return ignore.ignore(root, ignorefiles, sys.stderr.write) 1838bcea973SRichard Lowe else: 1848bcea973SRichard Lowe return lambda x: False 1858bcea973SRichard Lowe 1868bcea973SRichard Lowe 1878bcea973SRichard Lowedef gen_files(root, parent, paths, exclude): 1888bcea973SRichard Lowe """Return a function producing file names, relative to the current 1898bcea973SRichard Lowe directory, of any file changed on this branch (limited to 'paths' if 1908bcea973SRichard Lowe requested), and excluding files for which exclude returns a true value """ 1918bcea973SRichard Lowe 1928bcea973SRichard Lowe # Taken entirely from Python 2.6's os.path.relpath which we would use if we 1938bcea973SRichard Lowe # could. 1948bcea973SRichard Lowe def relpath(path, here): 1958bcea973SRichard Lowe c = os.path.abspath(os.path.join(root, path)).split(os.path.sep) 1968bcea973SRichard Lowe s = os.path.abspath(here).split(os.path.sep) 1978bcea973SRichard Lowe l = len(os.path.commonprefix((s, c))) 1988bcea973SRichard Lowe return os.path.join(*[os.path.pardir] * (len(s)-l) + c[l:]) 1998bcea973SRichard Lowe 2008bcea973SRichard Lowe def ret(select=None): 2018bcea973SRichard Lowe if not select: 2028bcea973SRichard Lowe select = lambda x: True 2038bcea973SRichard Lowe 2048bcea973SRichard Lowe for f in git_file_list(parent, paths): 2058bcea973SRichard Lowe f = relpath(f, '.') 2068bcea973SRichard Lowe if (os.path.exists(f) and select(f) and not exclude(f)): 2078bcea973SRichard Lowe yield f 2088bcea973SRichard Lowe return ret 2098bcea973SRichard Lowe 2108bcea973SRichard Lowe 2118bcea973SRichard Lowedef comchk(root, parent, flist, output): 2128bcea973SRichard Lowe output.write("Comments:\n") 2138bcea973SRichard Lowe 2148bcea973SRichard Lowe return Comments.comchk(git_comments(parent), check_db=True, 2158bcea973SRichard Lowe output=output) 2168bcea973SRichard Lowe 2178bcea973SRichard Lowe 2188bcea973SRichard Lowedef mapfilechk(root, parent, flist, output): 2198bcea973SRichard Lowe ret = 0 2208bcea973SRichard Lowe 2218bcea973SRichard Lowe # We are interested in examining any file that has the following 2228bcea973SRichard Lowe # in its final path segment: 2238bcea973SRichard Lowe # - Contains the word 'mapfile' 2248bcea973SRichard Lowe # - Begins with 'map.' 2258bcea973SRichard Lowe # - Ends with '.map' 2268bcea973SRichard Lowe # We don't want to match unless these things occur in final path segment 2278bcea973SRichard Lowe # because directory names with these strings don't indicate a mapfile. 2288bcea973SRichard Lowe # We also ignore files with suffixes that tell us that the files 2298bcea973SRichard Lowe # are not mapfiles. 2308bcea973SRichard Lowe MapfileRE = re.compile(r'.*((mapfile[^/]*)|(/map\.+[^/]*)|(\.map))$', 2318bcea973SRichard Lowe re.IGNORECASE) 2328bcea973SRichard Lowe NotMapSuffixRE = re.compile(r'.*\.[ch]$', re.IGNORECASE) 2338bcea973SRichard Lowe 2348bcea973SRichard Lowe output.write("Mapfile comments:\n") 2358bcea973SRichard Lowe 2368bcea973SRichard Lowe for f in flist(lambda x: MapfileRE.match(x) and not 2378bcea973SRichard Lowe NotMapSuffixRE.match(x)): 2388bcea973SRichard Lowe fh = open(f, 'r') 2398bcea973SRichard Lowe ret |= Mapfile.mapfilechk(fh, output=output) 2408bcea973SRichard Lowe fh.close() 2418bcea973SRichard Lowe return ret 2428bcea973SRichard Lowe 2438bcea973SRichard Lowe 2448bcea973SRichard Lowedef copyright(root, parent, flist, output): 2458bcea973SRichard Lowe ret = 0 2468bcea973SRichard Lowe output.write("Copyrights:\n") 2478bcea973SRichard Lowe for f in flist(): 2488bcea973SRichard Lowe fh = open(f, 'r') 2498bcea973SRichard Lowe ret |= Copyright.copyright(fh, output=output) 2508bcea973SRichard Lowe fh.close() 2518bcea973SRichard Lowe return ret 2528bcea973SRichard Lowe 2538bcea973SRichard Lowe 2548bcea973SRichard Lowedef hdrchk(root, parent, flist, output): 2558bcea973SRichard Lowe ret = 0 2568bcea973SRichard Lowe output.write("Header format:\n") 2578bcea973SRichard Lowe for f in flist(lambda x: x.endswith('.h')): 2588bcea973SRichard Lowe fh = open(f, 'r') 2598bcea973SRichard Lowe ret |= HdrChk.hdrchk(fh, lenient=True, output=output) 2608bcea973SRichard Lowe fh.close() 2618bcea973SRichard Lowe return ret 2628bcea973SRichard Lowe 2638bcea973SRichard Lowe 2648bcea973SRichard Lowedef cstyle(root, parent, flist, output): 2658bcea973SRichard Lowe ret = 0 2668bcea973SRichard Lowe output.write("C style:\n") 2678bcea973SRichard Lowe for f in flist(lambda x: x.endswith('.c') or x.endswith('.h')): 2688bcea973SRichard Lowe fh = open(f, 'r') 2698bcea973SRichard Lowe ret |= CStyle.cstyle(fh, output=output, picky=True, 2708bcea973SRichard Lowe check_posix_types=True, 2718bcea973SRichard Lowe check_continuation=True) 2728bcea973SRichard Lowe fh.close() 2738bcea973SRichard Lowe return ret 2748bcea973SRichard Lowe 2758bcea973SRichard Lowe 2768bcea973SRichard Lowedef jstyle(root, parent, flist, output): 2778bcea973SRichard Lowe ret = 0 2788bcea973SRichard Lowe output.write("Java style:\n") 2798bcea973SRichard Lowe for f in flist(lambda x: x.endswith('.java')): 2808bcea973SRichard Lowe fh = open(f, 'r') 2818bcea973SRichard Lowe ret |= JStyle.jstyle(fh, output=output, picky=True) 2828bcea973SRichard Lowe fh.close() 2838bcea973SRichard Lowe return ret 2848bcea973SRichard Lowe 2858bcea973SRichard Lowe 286*95c635efSGarrett D'Amoredef manlint(root, parent, flist, output): 287*95c635efSGarrett D'Amore ret = 0 288*95c635efSGarrett D'Amore output.write("Man page format:\n") 289*95c635efSGarrett D'Amore ManfileRE = re.compile(r'.*\.[0-9][a-z]*$', re.IGNORECASE) 290*95c635efSGarrett D'Amore for f in flist(lambda x: ManfileRE.match(x)): 291*95c635efSGarrett D'Amore fh = open(f, 'r') 292*95c635efSGarrett D'Amore ret |= ManLint.manlint(fh, output=output, picky=True) 293*95c635efSGarrett D'Amore fh.close() 294*95c635efSGarrett D'Amore return ret 295*95c635efSGarrett D'Amore 2968bcea973SRichard Lowedef keywords(root, parent, flist, output): 2978bcea973SRichard Lowe ret = 0 2988bcea973SRichard Lowe output.write("SCCS Keywords:\n") 2998bcea973SRichard Lowe for f in flist(): 3008bcea973SRichard Lowe fh = open(f, 'r') 3018bcea973SRichard Lowe ret |= Keywords.keywords(fh, output=output) 3028bcea973SRichard Lowe fh.close() 3038bcea973SRichard Lowe return ret 3048bcea973SRichard Lowe 3058bcea973SRichard Lowe 3068bcea973SRichard Lowedef run_checks(root, parent, cmds, paths='', opts={}): 3078bcea973SRichard Lowe """Run the checks given in 'cmds', expected to have well-known signatures, 3088bcea973SRichard Lowe and report results for any which fail. 3098bcea973SRichard Lowe 3108bcea973SRichard Lowe Return failure if any of them did. 3118bcea973SRichard Lowe 3128bcea973SRichard Lowe NB: the function name of the commands passed in is used to name the NOT 3138bcea973SRichard Lowe file which excepts files from them.""" 3148bcea973SRichard Lowe 3158bcea973SRichard Lowe ret = 0 3168bcea973SRichard Lowe 3178bcea973SRichard Lowe for cmd in cmds: 3188bcea973SRichard Lowe s = StringIO() 3198bcea973SRichard Lowe 3208bcea973SRichard Lowe exclude = not_check(root, cmd.func_name) 3218bcea973SRichard Lowe result = cmd(root, parent, gen_files(root, parent, paths, exclude), 3228bcea973SRichard Lowe output=s) 3238bcea973SRichard Lowe ret |= result 3248bcea973SRichard Lowe 3258bcea973SRichard Lowe if result != 0: 3268bcea973SRichard Lowe print s.getvalue() 3278bcea973SRichard Lowe 3288bcea973SRichard Lowe return ret 3298bcea973SRichard Lowe 3308bcea973SRichard Lowe 3318bcea973SRichard Lowedef nits(root, parent, paths): 3328bcea973SRichard Lowe cmds = [copyright, 3338bcea973SRichard Lowe cstyle, 3348bcea973SRichard Lowe hdrchk, 3358bcea973SRichard Lowe jstyle, 3368bcea973SRichard Lowe keywords, 337*95c635efSGarrett D'Amore manlint, 3388bcea973SRichard Lowe mapfilechk] 3398bcea973SRichard Lowe run_checks(root, parent, cmds, paths) 3408bcea973SRichard Lowe 3418bcea973SRichard Lowe 3428bcea973SRichard Lowedef pbchk(root, parent, paths): 3438bcea973SRichard Lowe cmds = [comchk, 3448bcea973SRichard Lowe copyright, 3458bcea973SRichard Lowe cstyle, 3468bcea973SRichard Lowe hdrchk, 3478bcea973SRichard Lowe jstyle, 3488bcea973SRichard Lowe keywords, 349*95c635efSGarrett D'Amore manlint, 3508bcea973SRichard Lowe mapfilechk] 3518bcea973SRichard Lowe run_checks(root, parent, cmds) 3528bcea973SRichard Lowe 3538bcea973SRichard Lowe 3548bcea973SRichard Lowedef main(cmd, args): 3558bcea973SRichard Lowe parent_branch = None 3568bcea973SRichard Lowe 3578bcea973SRichard Lowe try: 3588bcea973SRichard Lowe opts, args = getopt.getopt(args, 'b:') 3598bcea973SRichard Lowe except getopt.GetoptError, e: 3608bcea973SRichard Lowe sys.stderr.write(str(e) + '\n') 3618bcea973SRichard Lowe sys.stderr.write("Usage: %s [-b branch] [path...]\n" % cmd) 3628bcea973SRichard Lowe sys.exit(1) 3638bcea973SRichard Lowe 3648bcea973SRichard Lowe for opt, arg in opts: 3658bcea973SRichard Lowe if opt == '-b': 3668bcea973SRichard Lowe parent_branch = arg 3678bcea973SRichard Lowe 3688bcea973SRichard Lowe if not parent_branch: 3698bcea973SRichard Lowe parent_branch = git_parent_branch(git_branch()) 3708bcea973SRichard Lowe 3718bcea973SRichard Lowe func = nits 3728bcea973SRichard Lowe if cmd == 'git-pbchk': 3738bcea973SRichard Lowe func = pbchk 3748bcea973SRichard Lowe if args: 3758bcea973SRichard Lowe sys.stderr.write("only complete workspaces may be pbchk'd\n"); 3768bcea973SRichard Lowe sys.exit(1) 3778bcea973SRichard Lowe 3788bcea973SRichard Lowe func(git_root(), parent_branch, args) 3798bcea973SRichard Lowe 3808bcea973SRichard Loweif __name__ == '__main__': 3818bcea973SRichard Lowe try: 3828bcea973SRichard Lowe main(os.path.basename(sys.argv[0]), sys.argv[1:]) 3838bcea973SRichard Lowe except GitError, e: 3848bcea973SRichard Lowe sys.stderr.write("failed to run git:\n %s\n" % str(e)) 3858bcea973SRichard Lowe sys.exit(1) 386