13f770aabSAndy Fiddaman#!@TOOLS_PYTHON@ -Es 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 20955eb5e1SGarrett D'Amore# Copyright 2019 Garrett D'Amore <garrett@damore.org> 2193d2a904SPaul Dagnelie# Copyright (c) 2015, 2016 by Delphix. All rights reserved. 2228e2b3adSHans Rosenfeld# Copyright 2016 Nexenta Systems, Inc. 23972282a0SJohn Levon# Copyright (c) 2019, Joyent, Inc. 2413904da8SAndy Fiddaman# Copyright 2021 OmniOS Community Edition (OmniOSce) Association. 259af2fe54SBill Sommerfeld# Copyright 2024 Bill Sommerfeld 268bcea973SRichard Lowe# 278bcea973SRichard Lowe 28ca13eaa5SAndy Fiddamanfrom __future__ import print_function 29ca13eaa5SAndy Fiddaman 308bcea973SRichard Loweimport getopt 31ca13eaa5SAndy Fiddamanimport io 328bcea973SRichard Loweimport os 338bcea973SRichard Loweimport re 348bcea973SRichard Loweimport subprocess 358bcea973SRichard Loweimport sys 36ff50e8e5SRichard Loweimport tempfile 37*28e88f55SBill Sommerfeldimport textwrap 388bcea973SRichard Lowe 39ca13eaa5SAndy Fiddamanif sys.version_info[0] < 3: 40ca13eaa5SAndy Fiddaman from cStringIO import StringIO 41ca13eaa5SAndy Fiddamanelse: 42ca13eaa5SAndy Fiddaman from io import StringIO 438bcea973SRichard Lowe 448bcea973SRichard Lowe# 458bcea973SRichard Lowe# Adjust the load path based on our location and the version of python into 468bcea973SRichard Lowe# which it is being loaded. This assumes the normal onbld directory 478bcea973SRichard Lowe# structure, where we are in bin/ and the modules are in 488bcea973SRichard Lowe# lib/python(version)?/onbld/Scm/. If that changes so too must this. 498bcea973SRichard Lowe# 508bcea973SRichard Lowesys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "lib", 518bcea973SRichard Lowe "python%d.%d" % sys.version_info[:2])) 528bcea973SRichard Lowe 538bcea973SRichard Lowe# 548bcea973SRichard Lowe# Add the relative path to usr/src/tools to the load path, such that when run 558bcea973SRichard Lowe# from the source tree we use the modules also within the source tree. 568bcea973SRichard Lowe# 578bcea973SRichard Lowesys.path.insert(2, os.path.join(os.path.dirname(__file__), "..")) 588bcea973SRichard Lowe 59e5587435SJoshua M. Clulowfrom onbld.Scm import Ignore 604ff15898SGordon Rossfrom onbld.Checks import Comments, Copyright, CStyle, HdrChk, WsCheck 6171af3be3SCody Peter Mellofrom onbld.Checks import JStyle, Keywords, ManLint, Mapfile, SpellCheck 6286d41711SAndy Fiddamanfrom onbld.Checks import ShellLint, PkgFmt 638bcea973SRichard Lowe 648bcea973SRichard Loweclass GitError(Exception): 658bcea973SRichard Lowe pass 668bcea973SRichard Lowe 678bcea973SRichard Lowedef git(command): 688bcea973SRichard Lowe """Run a command and return a stream containing its stdout (and write its 698bcea973SRichard Lowe stderr to its stdout)""" 708bcea973SRichard Lowe 718bcea973SRichard Lowe if type(command) != list: 728bcea973SRichard Lowe command = command.split() 738bcea973SRichard Lowe 748bcea973SRichard Lowe command = ["git"] + command 758bcea973SRichard Lowe 76ff50e8e5SRichard Lowe try: 77ca13eaa5SAndy Fiddaman tmpfile = tempfile.TemporaryFile(prefix="git-nits", mode="w+b") 78ca13eaa5SAndy Fiddaman except EnvironmentError as e: 79ff50e8e5SRichard Lowe raise GitError("Could not create temporary file: %s\n" % e) 80ff50e8e5SRichard Lowe 81ff50e8e5SRichard Lowe try: 82ff50e8e5SRichard Lowe p = subprocess.Popen(command, 83ff50e8e5SRichard Lowe stdout=tmpfile, 84380fd671SMatthew Ahrens stderr=subprocess.PIPE) 85ca13eaa5SAndy Fiddaman except OSError as e: 86709afb1dSDillon Amburgey raise GitError("could not execute %s: %s\n" % (command, e)) 878bcea973SRichard Lowe 888bcea973SRichard Lowe err = p.wait() 898bcea973SRichard Lowe if err != 0: 90380fd671SMatthew Ahrens raise GitError(p.stderr.read()) 918bcea973SRichard Lowe 92ff50e8e5SRichard Lowe tmpfile.seek(0) 93ca13eaa5SAndy Fiddaman lines = [] 94ca13eaa5SAndy Fiddaman for l in tmpfile: 95ca13eaa5SAndy Fiddaman lines.append(l.decode('utf-8', 'replace')) 96ca13eaa5SAndy Fiddaman return lines 978bcea973SRichard Lowe 988bcea973SRichard Lowedef git_root(): 998bcea973SRichard Lowe """Return the root of the current git workspace""" 1008bcea973SRichard Lowe 1019af2fe54SBill Sommerfeld p = git('rev-parse --show-toplevel') 1029af2fe54SBill Sommerfeld dir = p[0].strip() 1038bcea973SRichard Lowe 1049af2fe54SBill Sommerfeld return os.path.abspath(dir) 1058bcea973SRichard Lowe 1068bcea973SRichard Lowedef git_branch(): 1078bcea973SRichard Lowe """Return the current git branch""" 1088bcea973SRichard Lowe 1098bcea973SRichard Lowe p = git('branch') 1108bcea973SRichard Lowe 1118bcea973SRichard Lowe for elt in p: 1128bcea973SRichard Lowe if elt[0] == '*': 1138bcea973SRichard Lowe if elt.endswith('(no branch)'): 1148bcea973SRichard Lowe return None 1158bcea973SRichard Lowe return elt.split()[1] 1168bcea973SRichard Lowe 1178bcea973SRichard Lowedef git_parent_branch(branch): 1188bcea973SRichard Lowe """Return the parent of the current git branch. 1198bcea973SRichard Lowe 1208bcea973SRichard Lowe If this branch tracks a remote branch, return the remote branch which is 1218bcea973SRichard Lowe tracked. If not, default to origin/master.""" 1228bcea973SRichard Lowe 1238bcea973SRichard Lowe if not branch: 1248bcea973SRichard Lowe return None 1258bcea973SRichard Lowe 1264ff15898SGordon Ross p = git(["for-each-ref", "--format=%(refname:short) %(upstream:short)", 12728e2b3adSHans Rosenfeld "refs/heads/"]) 1288bcea973SRichard Lowe 1298bcea973SRichard Lowe if not p: 1308bcea973SRichard Lowe sys.stderr.write("Failed finding git parent branch\n") 131972282a0SJohn Levon sys.exit(1) 1328bcea973SRichard Lowe 1338bcea973SRichard Lowe for line in p: 1348bcea973SRichard Lowe # Git 1.7 will leave a ' ' trailing any non-tracking branch 1358bcea973SRichard Lowe if ' ' in line and not line.endswith(' \n'): 1368bcea973SRichard Lowe local, remote = line.split() 1378bcea973SRichard Lowe if local == branch: 1388bcea973SRichard Lowe return remote 1398bcea973SRichard Lowe return 'origin/master' 1408bcea973SRichard Lowe 1413f8945a7SBill Sommerfelddef slices(strlist, sep): 1423f8945a7SBill Sommerfeld """Yield start & end of each commit within the list of comments""" 1433f8945a7SBill Sommerfeld low = 0 1443f8945a7SBill Sommerfeld for i, v in enumerate(strlist): 1453f8945a7SBill Sommerfeld if v == sep: 1463f8945a7SBill Sommerfeld yield(low, i) 1473f8945a7SBill Sommerfeld low = i+1 1483f8945a7SBill Sommerfeld 1493f8945a7SBill Sommerfeld if low != len(strlist): 1503f8945a7SBill Sommerfeld yield(low, len(strlist)) 1513f8945a7SBill Sommerfeld 1528bcea973SRichard Lowedef git_comments(parent): 1533f8945a7SBill Sommerfeld """Return the checkin comments for each commit on this git branch, 1543f8945a7SBill Sommerfeld structured as a list of lists of lines.""" 1558bcea973SRichard Lowe 15627495383SRichard Lowe p = git('log --pretty=tformat:%%B:SEP: %s..' % parent) 1578bcea973SRichard Lowe 1588bcea973SRichard Lowe if not p: 159972282a0SJohn Levon sys.stderr.write("No outgoing changesets found - missing -p option?\n"); 160972282a0SJohn Levon sys.exit(1) 1618bcea973SRichard Lowe 1623f8945a7SBill Sommerfeld return [ [line.strip() for line in p[a:b]] 1633f8945a7SBill Sommerfeld for (a, b) in slices(p, ':SEP:\n')] 1648bcea973SRichard Lowe 1658bcea973SRichard Lowedef git_file_list(parent, paths=None): 1668bcea973SRichard Lowe """Return the set of files which have ever changed on this branch. 1678bcea973SRichard Lowe 1688bcea973SRichard Lowe NB: This includes files which no longer exist, or no longer actually 1698bcea973SRichard Lowe differ.""" 1708bcea973SRichard Lowe 1718bcea973SRichard Lowe p = git("log --name-only --pretty=format: %s.. %s" % 1728bcea973SRichard Lowe (parent, ' '.join(paths))) 1738bcea973SRichard Lowe 1748bcea973SRichard Lowe if not p: 1758bcea973SRichard Lowe sys.stderr.write("Failed building file-list from git\n") 176972282a0SJohn Levon sys.exit(1) 1778bcea973SRichard Lowe 1788bcea973SRichard Lowe ret = set() 1798bcea973SRichard Lowe for fname in p: 180da88d39fSBill Sommerfeld fname = fname.strip() 181da88d39fSBill Sommerfeld if fname and not fname.isspace(): 182da88d39fSBill Sommerfeld ret.add(fname) 1838bcea973SRichard Lowe 184da88d39fSBill Sommerfeld return sorted(ret) 1858bcea973SRichard Lowe 1868bcea973SRichard Lowedef not_check(root, cmd): 1878bcea973SRichard Lowe """Return a function which returns True if a file given as an argument 1888bcea973SRichard Lowe should be excluded from the check named by 'cmd'""" 1898bcea973SRichard Lowe 190ca13eaa5SAndy Fiddaman ignorefiles = list(filter(os.path.exists, 191c82c4676SGordon Ross [os.path.join(root, ".git/info", "%s.NOT" % cmd), 192ca13eaa5SAndy Fiddaman os.path.join(root, "exception_lists", cmd)])) 193e5587435SJoshua M. Clulow return Ignore.ignore(root, ignorefiles) 1948bcea973SRichard Lowe 195955eb5e1SGarrett D'Amoredef gen_files(root, parent, paths, exclude, filter=None): 1968bcea973SRichard Lowe """Return a function producing file names, relative to the current 1978bcea973SRichard Lowe directory, of any file changed on this branch (limited to 'paths' if 1988bcea973SRichard Lowe requested), and excluding files for which exclude returns a true value """ 1998bcea973SRichard Lowe 200955eb5e1SGarrett D'Amore if filter is None: 201955eb5e1SGarrett D'Amore filter = lambda x: os.path.isfile(x) 202955eb5e1SGarrett D'Amore 2038bcea973SRichard Lowe def ret(select=None): 2048bcea973SRichard Lowe if not select: 2058bcea973SRichard Lowe select = lambda x: True 2068bcea973SRichard Lowe 20738e36c53SJohn Levon for abspath in git_file_list(parent, paths): 2089af2fe54SBill Sommerfeld path = os.path.relpath(os.path.join(root, abspath), '.') 20993d2a904SPaul Dagnelie try: 21038e36c53SJohn Levon res = git("diff %s HEAD %s" % (parent, path)) 211ca13eaa5SAndy Fiddaman except GitError as e: 21238e36c53SJohn Levon # This ignores all the errors that can be thrown. Usually, this 21338e36c53SJohn Levon # means that git returned non-zero because the file doesn't 21438e36c53SJohn Levon # exist, but it could also fail if git can't create a new file 21538e36c53SJohn Levon # or it can't be executed. Such errors are 1) unlikely, and 2) 21638e36c53SJohn Levon # will be caught by other invocations of git(). 21793d2a904SPaul Dagnelie continue 218ca13eaa5SAndy Fiddaman empty = not res 219955eb5e1SGarrett D'Amore if (filter(path) and not empty and 22038e36c53SJohn Levon select(path) and not exclude(abspath)): 22138e36c53SJohn Levon yield path 2228bcea973SRichard Lowe return ret 2238bcea973SRichard Lowe 224955eb5e1SGarrett D'Amoredef gen_links(root, parent, paths, exclude): 225955eb5e1SGarrett D'Amore """Return a function producing symbolic link names, relative to the current 226955eb5e1SGarrett D'Amore directory, of any file changed on this branch (limited to 'paths' if 227955eb5e1SGarrett D'Amore requested), and excluding files for which exclude returns a true value """ 228955eb5e1SGarrett D'Amore 229955eb5e1SGarrett D'Amore return gen_files(root, parent, paths, exclude, lambda x: os.path.islink(x)) 230955eb5e1SGarrett D'Amore 231*28e88f55SBill Sommerfelddef gen_none(root, parent, paths, exclude): 232*28e88f55SBill Sommerfeld """ Return a function returning the empty list """ 233*28e88f55SBill Sommerfeld return lambda x: [] 234*28e88f55SBill Sommerfeld 235*28e88f55SBill Sommerfeld# The list of possible checks. Each is recorded as two-function pair; the 236*28e88f55SBill Sommerfeld# first is the actual checker, and the second is the generator which creates 237*28e88f55SBill Sommerfeld# the list of things that the checker works on. 238*28e88f55SBill Sommerfeld 239*28e88f55SBill Sommerfeldchecks = {} 240*28e88f55SBill Sommerfeldnits_checks = [] 241*28e88f55SBill Sommerfeldall_checks = [] 242*28e88f55SBill Sommerfeld 243*28e88f55SBill Sommerfelddef add_check(fn, gen): 244*28e88f55SBill Sommerfeld """ Define a checker and add it to the appropriate lists """ 245*28e88f55SBill Sommerfeld name = fn.__name__ 246*28e88f55SBill Sommerfeld if fn.__doc__ is None: 247*28e88f55SBill Sommerfeld raise ValueError('Check function lacks a documentation string', 248*28e88f55SBill Sommerfeld name) 249*28e88f55SBill Sommerfeld checks[name] = (fn, gen) 250*28e88f55SBill Sommerfeld all_checks.append(name) 251*28e88f55SBill Sommerfeld if gen != gen_none: 252*28e88f55SBill Sommerfeld nits_checks.append(name) 253*28e88f55SBill Sommerfeld return fn 254*28e88f55SBill Sommerfeld 255*28e88f55SBill Sommerfelddef filechecker(fn): 256*28e88f55SBill Sommerfeld """ Decorator which identifies a function as being a file-checker """ 257*28e88f55SBill Sommerfeld return add_check(fn, gen_files) 258*28e88f55SBill Sommerfeld 259*28e88f55SBill Sommerfelddef linkchecker(fn): 260*28e88f55SBill Sommerfeld """ Decorator which identifies a function as being a symlink-checker """ 261*28e88f55SBill Sommerfeld return add_check(fn, gen_links) 262*28e88f55SBill Sommerfeld 263*28e88f55SBill Sommerfelddef wschecker(fn): 264*28e88f55SBill Sommerfeld """ Decorator which identifies a function as being a workspace checker """ 265*28e88f55SBill Sommerfeld return add_check(fn, gen_none) 266*28e88f55SBill Sommerfeld 267*28e88f55SBill Sommerfeld@wschecker 2688bcea973SRichard Lowedef comchk(root, parent, flist, output): 269*28e88f55SBill Sommerfeld "Check that putback comments follow the prescribed format" 2708bcea973SRichard Lowe output.write("Comments:\n") 2718bcea973SRichard Lowe 2728d226a82SBill Sommerfeld comments = git_comments(parent) 2733f8945a7SBill Sommerfeld multi = len(comments) > 1 2743f8945a7SBill Sommerfeld state = {} 2753f8945a7SBill Sommerfeld 2763f8945a7SBill Sommerfeld ret = 0 2773f8945a7SBill Sommerfeld for commit in comments: 2788d226a82SBill Sommerfeld 2793f8945a7SBill Sommerfeld s = StringIO() 2808bcea973SRichard Lowe 2813f8945a7SBill Sommerfeld result = Comments.comchk(commit, check_db=True, 2823f8945a7SBill Sommerfeld output=s, bugs=state) 2833f8945a7SBill Sommerfeld ret |= result 2843f8945a7SBill Sommerfeld 2853f8945a7SBill Sommerfeld if result != 0: 2863f8945a7SBill Sommerfeld if multi: 2873f8945a7SBill Sommerfeld output.write('\n%s\n' % commit[0]) 2883f8945a7SBill Sommerfeld output.write(s.getvalue()) 2893f8945a7SBill Sommerfeld 2903f8945a7SBill Sommerfeld return ret 2918bcea973SRichard Lowe 292*28e88f55SBill Sommerfeld@filechecker 2938bcea973SRichard Lowedef copyright(root, parent, flist, output): 294*28e88f55SBill Sommerfeld """Check that each source file contains a copyright notice for the current 295*28e88f55SBill Sommerfeldyear. You don't need to fix this if you, the potential new copyright holder, 296*28e88f55SBill Sommerfeldchooses not to.""" 2978bcea973SRichard Lowe ret = 0 2988bcea973SRichard Lowe output.write("Copyrights:\n") 2998bcea973SRichard Lowe for f in flist(): 300ca13eaa5SAndy Fiddaman with io.open(f, encoding='utf-8', errors='replace') as fh: 301ca13eaa5SAndy Fiddaman ret |= Copyright.copyright(fh, output=output) 3028bcea973SRichard Lowe return ret 3038bcea973SRichard Lowe 304*28e88f55SBill Sommerfeld@filechecker 3058bcea973SRichard Lowedef cstyle(root, parent, flist, output): 306*28e88f55SBill Sommerfeld "Check that C source files conform to the illumos C style rules" 3078bcea973SRichard Lowe ret = 0 3088bcea973SRichard Lowe output.write("C style:\n") 3098bcea973SRichard Lowe for f in flist(lambda x: x.endswith('.c') or x.endswith('.h')): 310a90997d2SAndy Fiddaman with io.open(f, mode='rb') as fh: 311ca13eaa5SAndy Fiddaman ret |= CStyle.cstyle(fh, output=output, picky=True, 3128bcea973SRichard Lowe check_posix_types=True, 3138bcea973SRichard Lowe check_continuation=True) 3148bcea973SRichard Lowe return ret 3158bcea973SRichard Lowe 316*28e88f55SBill Sommerfeld@filechecker 317*28e88f55SBill Sommerfelddef hdrchk(root, parent, flist, output): 318*28e88f55SBill Sommerfeld "Check that C header files conform to the illumos header style rules" 319*28e88f55SBill Sommerfeld ret = 0 320*28e88f55SBill Sommerfeld output.write("Header format:\n") 321*28e88f55SBill Sommerfeld for f in flist(lambda x: x.endswith('.h')): 322*28e88f55SBill Sommerfeld with io.open(f, encoding='utf-8', errors='replace') as fh: 323*28e88f55SBill Sommerfeld ret |= HdrChk.hdrchk(fh, lenient=True, output=output) 324*28e88f55SBill Sommerfeld return ret 325*28e88f55SBill Sommerfeld 326*28e88f55SBill Sommerfeld@filechecker 3278bcea973SRichard Lowedef jstyle(root, parent, flist, output): 328*28e88f55SBill Sommerfeld """Check that Java source files conform to the illumos Java style rules 329*28e88f55SBill Sommerfeld(which differ from the traditionally recommended Java style)""" 330*28e88f55SBill Sommerfeld 3318bcea973SRichard Lowe ret = 0 3328bcea973SRichard Lowe output.write("Java style:\n") 3338bcea973SRichard Lowe for f in flist(lambda x: x.endswith('.java')): 33410811751SAndy Fiddaman with io.open(f, mode='rb') as fh: 335ca13eaa5SAndy Fiddaman ret |= JStyle.jstyle(fh, output=output, picky=True) 3368bcea973SRichard Lowe return ret 3378bcea973SRichard Lowe 338*28e88f55SBill Sommerfeld@filechecker 339*28e88f55SBill Sommerfelddef keywords(root, parent, flist, output): 340*28e88f55SBill Sommerfeld """Check that no source files contain unexpanded SCCS keywords. 341*28e88f55SBill SommerfeldIt is possible that this check may false positive on certain inputs. 342*28e88f55SBill SommerfeldIt is generally obvious when this is the case. 343*28e88f55SBill Sommerfeld 344*28e88f55SBill SommerfeldThis check does not check for expanded SCCS keywords, though the common 345*28e88f55SBill Sommerfeld'ident'-style lines should be removed regardless of whether they are 346*28e88f55SBill Sommerfeldexpanded.""" 347*28e88f55SBill Sommerfeld 348*28e88f55SBill Sommerfeld ret = 0 349*28e88f55SBill Sommerfeld output.write("SCCS Keywords:\n") 350*28e88f55SBill Sommerfeld for f in flist(): 351*28e88f55SBill Sommerfeld with io.open(f, encoding='utf-8', errors='replace') as fh: 352*28e88f55SBill Sommerfeld ret |= Keywords.keywords(fh, output=output) 353*28e88f55SBill Sommerfeld return ret 354*28e88f55SBill Sommerfeld 355*28e88f55SBill Sommerfeld@filechecker 35695c635efSGarrett D'Amoredef manlint(root, parent, flist, output): 357*28e88f55SBill Sommerfeld "Check for problems with man pages." 358*28e88f55SBill Sommerfeld 35995c635efSGarrett D'Amore ret = 0 36071af3be3SCody Peter Mello output.write("Man page format/spelling:\n") 36195c635efSGarrett D'Amore ManfileRE = re.compile(r'.*\.[0-9][a-z]*$', re.IGNORECASE) 36295c635efSGarrett D'Amore for f in flist(lambda x: ManfileRE.match(x)): 363a90997d2SAndy Fiddaman with io.open(f, mode='rb') as fh: 364ca13eaa5SAndy Fiddaman ret |= ManLint.manlint(fh, output=output, picky=True) 365ca13eaa5SAndy Fiddaman ret |= SpellCheck.spellcheck(fh, output=output) 36695c635efSGarrett D'Amore return ret 36795c635efSGarrett D'Amore 368*28e88f55SBill Sommerfeld@filechecker 369*28e88f55SBill Sommerfelddef mapfilechk(root, parent, flist, output): 370*28e88f55SBill Sommerfeld """Check that linker mapfiles contain a comment directing anyone 371*28e88f55SBill Sommerfeldediting to read the directions in usr/lib/README.mapfiles.""" 372*28e88f55SBill Sommerfeld 373*28e88f55SBill Sommerfeld ret = 0 374*28e88f55SBill Sommerfeld # We are interested in examining any file that has the following 375*28e88f55SBill Sommerfeld # in its final path segment: 376*28e88f55SBill Sommerfeld # - Contains the word 'mapfile' 377*28e88f55SBill Sommerfeld # - Begins with 'map.' 378*28e88f55SBill Sommerfeld # - Ends with '.map' 379*28e88f55SBill Sommerfeld # We don't want to match unless these things occur in final path segment 380*28e88f55SBill Sommerfeld # because directory names with these strings don't indicate a mapfile. 381*28e88f55SBill Sommerfeld # We also ignore files with suffixes that tell us that the files 382*28e88f55SBill Sommerfeld # are not mapfiles. 383*28e88f55SBill Sommerfeld MapfileRE = re.compile(r'.*((mapfile[^/]*)|(/map\.+[^/]*)|(\.map))$', 384*28e88f55SBill Sommerfeld re.IGNORECASE) 385*28e88f55SBill Sommerfeld NotMapSuffixRE = re.compile(r'.*\.[ch]$', re.IGNORECASE) 386*28e88f55SBill Sommerfeld 387*28e88f55SBill Sommerfeld output.write("Mapfile comments:\n") 388*28e88f55SBill Sommerfeld 389*28e88f55SBill Sommerfeld for f in flist(lambda x: MapfileRE.match(x) and not 390*28e88f55SBill Sommerfeld NotMapSuffixRE.match(x)): 391*28e88f55SBill Sommerfeld with io.open(f, encoding='utf-8', errors='replace') as fh: 392*28e88f55SBill Sommerfeld ret |= Mapfile.mapfilechk(fh, output=output) 393*28e88f55SBill Sommerfeld return ret 394*28e88f55SBill Sommerfeld 395*28e88f55SBill Sommerfeld@filechecker 39613904da8SAndy Fiddamandef shelllint(root, parent, flist, output): 397*28e88f55SBill Sommerfeld """Check shell scripts for common errors.""" 39813904da8SAndy Fiddaman ret = 0 39913904da8SAndy Fiddaman output.write("Shell lint:\n") 40013904da8SAndy Fiddaman 40113904da8SAndy Fiddaman def isshell(x): 40213904da8SAndy Fiddaman (_, ext) = os.path.splitext(x) 40313904da8SAndy Fiddaman if ext in ['.sh', '.ksh']: 40413904da8SAndy Fiddaman return True 40513904da8SAndy Fiddaman if ext == '': 40613904da8SAndy Fiddaman with io.open(x, mode='r', errors='ignore') as fh: 40713904da8SAndy Fiddaman if re.match(r'^#.*\bk?sh\b', fh.readline()): 40813904da8SAndy Fiddaman return True 40913904da8SAndy Fiddaman return False 41013904da8SAndy Fiddaman 41113904da8SAndy Fiddaman for f in flist(isshell): 41213904da8SAndy Fiddaman with io.open(f, mode='rb') as fh: 41313904da8SAndy Fiddaman ret |= ShellLint.lint(fh, output=output) 41413904da8SAndy Fiddaman 41513904da8SAndy Fiddaman return ret 41613904da8SAndy Fiddaman 417*28e88f55SBill Sommerfeld@filechecker 41886d41711SAndy Fiddamandef pkgfmt(root, parent, flist, output): 419*28e88f55SBill Sommerfeld """Check package manifests for common errors.""" 42086d41711SAndy Fiddaman ret = 0 42186d41711SAndy Fiddaman output.write("Package manifests:\n") 42286d41711SAndy Fiddaman 42325b05a3eSAndy Fiddaman for f in flist(lambda x: x.endswith('.p5m')): 42486d41711SAndy Fiddaman with io.open(f, mode='rb') as fh: 42586d41711SAndy Fiddaman ret |= PkgFmt.check(fh, output=output) 42686d41711SAndy Fiddaman 42786d41711SAndy Fiddaman return ret 42886d41711SAndy Fiddaman 429955eb5e1SGarrett D'Amoredef iswinreserved(name): 430955eb5e1SGarrett D'Amore reserved = [ 431955eb5e1SGarrett D'Amore 'con', 'prn', 'aux', 'nul', 432955eb5e1SGarrett D'Amore 'com1', 'com2', 'com3', 'com4', 'com5', 433955eb5e1SGarrett D'Amore 'com6', 'com7', 'com8', 'com9', 'com0', 434955eb5e1SGarrett D'Amore 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 435955eb5e1SGarrett D'Amore 'lpt6', 'lpt7', 'lpt8', 'lpt9', 'lpt0' ] 436955eb5e1SGarrett D'Amore l = name.lower() 437955eb5e1SGarrett D'Amore for r in reserved: 438955eb5e1SGarrett D'Amore if l == r or l.startswith(r+"."): 439955eb5e1SGarrett D'Amore return True 440955eb5e1SGarrett D'Amore return False 441955eb5e1SGarrett D'Amore 442955eb5e1SGarrett D'Amoredef haswinspecial(name): 443955eb5e1SGarrett D'Amore specials = '<>:"\\|?*' 444955eb5e1SGarrett D'Amore for c in name: 445955eb5e1SGarrett D'Amore if c in specials: 446955eb5e1SGarrett D'Amore return True 447955eb5e1SGarrett D'Amore return False 448955eb5e1SGarrett D'Amore 449*28e88f55SBill Sommerfeld@filechecker 450955eb5e1SGarrett D'Amoredef winnames(root, parent, flist, output): 451*28e88f55SBill Sommerfeld "Check for filenames which can't be used in a Windows filesystem." 452955eb5e1SGarrett D'Amore ret = 0 453955eb5e1SGarrett D'Amore output.write("Illegal filenames (Windows):\n") 454955eb5e1SGarrett D'Amore for f in flist(): 455955eb5e1SGarrett D'Amore if haswinspecial(f): 456955eb5e1SGarrett D'Amore output.write(" "+f+": invalid character in name\n") 457955eb5e1SGarrett D'Amore ret |= 1 458955eb5e1SGarrett D'Amore continue 459955eb5e1SGarrett D'Amore 460955eb5e1SGarrett D'Amore parts = f.split('/') 461955eb5e1SGarrett D'Amore for p in parts: 462955eb5e1SGarrett D'Amore if iswinreserved(p): 463955eb5e1SGarrett D'Amore output.write(" "+f+": reserved file name\n") 464955eb5e1SGarrett D'Amore ret |= 1 465955eb5e1SGarrett D'Amore break 466955eb5e1SGarrett D'Amore 467955eb5e1SGarrett D'Amore return ret 468955eb5e1SGarrett D'Amore 469*28e88f55SBill Sommerfeld@filechecker 470*28e88f55SBill Sommerfelddef wscheck(root, parent, flist, output): 471*28e88f55SBill Sommerfeld "Check for whitespace issues such as mixed tabs/spaces in source files." 472*28e88f55SBill Sommerfeld ret = 0 473*28e88f55SBill Sommerfeld output.write("white space nits:\n") 474*28e88f55SBill Sommerfeld for f in flist(): 475*28e88f55SBill Sommerfeld with io.open(f, encoding='utf-8', errors='replace') as fh: 476*28e88f55SBill Sommerfeld ret |= WsCheck.wscheck(fh, output=output) 477*28e88f55SBill Sommerfeld return ret 478*28e88f55SBill Sommerfeld 479*28e88f55SBill Sommerfeld@linkchecker 480*28e88f55SBill Sommerfelddef symlinks(root, parent, flist, output): 481*28e88f55SBill Sommerfeld "Check for committed symlinks (there shouldn't be any)." 482*28e88f55SBill Sommerfeld ret = 0 483*28e88f55SBill Sommerfeld output.write("Symbolic links:\n") 484*28e88f55SBill Sommerfeld for f in flist(): 485*28e88f55SBill Sommerfeld output.write(" "+f+"\n") 486*28e88f55SBill Sommerfeld ret |= 1 487*28e88f55SBill Sommerfeld return ret 488*28e88f55SBill Sommerfeld 489*28e88f55SBill Sommerfelddef run_checks(root, parent, checklist, paths=''): 490*28e88f55SBill Sommerfeld """Run the checks named in 'checklist', 4918bcea973SRichard Lowe and report results for any which fail. 4928bcea973SRichard Lowe 4938bcea973SRichard Lowe Return failure if any of them did. 4948bcea973SRichard Lowe 495*28e88f55SBill Sommerfeld NB: the check names also name the NOT 4968bcea973SRichard Lowe file which excepts files from them.""" 4978bcea973SRichard Lowe 4988bcea973SRichard Lowe ret = 0 4998bcea973SRichard Lowe 500*28e88f55SBill Sommerfeld for check in checklist: 501*28e88f55SBill Sommerfeld (cmd, gen) = checks[check] 5028bcea973SRichard Lowe 503955eb5e1SGarrett D'Amore s = StringIO() 504955eb5e1SGarrett D'Amore 505*28e88f55SBill Sommerfeld exclude = not_check(root, check) 506*28e88f55SBill Sommerfeld result = cmd(root, parent, gen(root, parent, paths, exclude), 507955eb5e1SGarrett D'Amore output=s) 508955eb5e1SGarrett D'Amore ret |= result 509955eb5e1SGarrett D'Amore 510955eb5e1SGarrett D'Amore if result != 0: 511955eb5e1SGarrett D'Amore print(s.getvalue()) 512955eb5e1SGarrett D'Amore 5138bcea973SRichard Lowe return ret 5148bcea973SRichard Lowe 515*28e88f55SBill Sommerfelddef print_checks(): 516*28e88f55SBill Sommerfeld 517*28e88f55SBill Sommerfeld for c in all_checks: 518*28e88f55SBill Sommerfeld print(textwrap.fill( 519*28e88f55SBill Sommerfeld "%-11s %s" % (c, checks[c][0].__doc__), 520*28e88f55SBill Sommerfeld width=78, 521*28e88f55SBill Sommerfeld subsequent_indent=' '*12), '\n') 5228bcea973SRichard Lowe 5238bcea973SRichard Lowedef main(cmd, args): 5248bcea973SRichard Lowe parent_branch = None 525*28e88f55SBill Sommerfeld 526*28e88f55SBill Sommerfeld checklist = [] 5278bcea973SRichard Lowe 5288bcea973SRichard Lowe try: 529*28e88f55SBill Sommerfeld opts, args = getopt.getopt(args, 'lb:c:p:') 530ca13eaa5SAndy Fiddaman except getopt.GetoptError as e: 5318bcea973SRichard Lowe sys.stderr.write(str(e) + '\n') 532*28e88f55SBill Sommerfeld sys.stderr.write("Usage: %s [-l] [-c check] [-p branch] [path...]\n" 533*28e88f55SBill Sommerfeld % cmd) 5348bcea973SRichard Lowe sys.exit(1) 5358bcea973SRichard Lowe 5368bcea973SRichard Lowe for opt, arg in opts: 537*28e88f55SBill Sommerfeld if opt == '-l': 538*28e88f55SBill Sommerfeld print_checks() 539*28e88f55SBill Sommerfeld sys.exit(0) 54042a3762dSJoshua M. Clulow # We accept "-b" as an alias of "-p" for backwards compatibility. 541*28e88f55SBill Sommerfeld elif opt == '-p' or opt == '-b': 5428bcea973SRichard Lowe parent_branch = arg 543eabe844aSJohn Levon elif opt == '-c': 544*28e88f55SBill Sommerfeld if arg not in checks: 545*28e88f55SBill Sommerfeld sys.stderr.write("Unknown check '%s'\n" % arg) 546*28e88f55SBill Sommerfeld sys.exit(1) 547*28e88f55SBill Sommerfeld checklist.append(arg) 5488bcea973SRichard Lowe 5498bcea973SRichard Lowe if not parent_branch: 5508bcea973SRichard Lowe parent_branch = git_parent_branch(git_branch()) 5518bcea973SRichard Lowe 552*28e88f55SBill Sommerfeld if len(checklist) == 0: 553eabe844aSJohn Levon if cmd == 'git-pbchk': 554*28e88f55SBill Sommerfeld if args: 555*28e88f55SBill Sommerfeld sys.stderr.write("only complete workspaces may be pbchk'd\n"); 556*28e88f55SBill Sommerfeld sys.exit(1) 557*28e88f55SBill Sommerfeld checklist = all_checks 558eabe844aSJohn Levon else: 559*28e88f55SBill Sommerfeld checklist = nits_checks 560*28e88f55SBill Sommerfeld 561*28e88f55SBill Sommerfeld run_checks(git_root(), parent_branch, checklist, args) 5628bcea973SRichard Lowe 5638bcea973SRichard Loweif __name__ == '__main__': 5648bcea973SRichard Lowe try: 5658bcea973SRichard Lowe main(os.path.basename(sys.argv[0]), sys.argv[1:]) 566ca13eaa5SAndy Fiddaman except GitError as e: 5678bcea973SRichard Lowe sys.stderr.write("failed to run git:\n %s\n" % str(e)) 5688bcea973SRichard Lowe sys.exit(1) 569