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 26*cbbc171dSiximeow# Copyright 2024 Oxide Computer Company 278bcea973SRichard Lowe# 288bcea973SRichard Lowe 29ca13eaa5SAndy Fiddamanfrom __future__ import print_function 30ca13eaa5SAndy Fiddaman 318bcea973SRichard Loweimport getopt 32ca13eaa5SAndy Fiddamanimport io 338bcea973SRichard Loweimport os 348bcea973SRichard Loweimport re 358bcea973SRichard Loweimport subprocess 368bcea973SRichard Loweimport sys 37ff50e8e5SRichard Loweimport tempfile 3828e88f55SBill Sommerfeldimport textwrap 398bcea973SRichard Lowe 40ca13eaa5SAndy Fiddamanif sys.version_info[0] < 3: 418bcea973SRichard Lowe from cStringIO import StringIO 42ca13eaa5SAndy Fiddamanelse: 43ca13eaa5SAndy Fiddaman from io import StringIO 448bcea973SRichard Lowe 458bcea973SRichard Lowe# 468bcea973SRichard Lowe# Adjust the load path based on our location and the version of python into 478bcea973SRichard Lowe# which it is being loaded. This assumes the normal onbld directory 488bcea973SRichard Lowe# structure, where we are in bin/ and the modules are in 498bcea973SRichard Lowe# lib/python(version)?/onbld/Scm/. If that changes so too must this. 508bcea973SRichard Lowe# 518bcea973SRichard Lowesys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "lib", 528bcea973SRichard Lowe "python%d.%d" % sys.version_info[:2])) 538bcea973SRichard Lowe 548bcea973SRichard Lowe# 558bcea973SRichard Lowe# Add the relative path to usr/src/tools to the load path, such that when run 568bcea973SRichard Lowe# from the source tree we use the modules also within the source tree. 578bcea973SRichard Lowe# 588bcea973SRichard Lowesys.path.insert(2, os.path.join(os.path.dirname(__file__), "..")) 598bcea973SRichard Lowe 60e5587435SJoshua M. Clulowfrom onbld.Scm import Ignore 614ff15898SGordon Rossfrom onbld.Checks import Comments, Copyright, CStyle, HdrChk, WsCheck 6271af3be3SCody Peter Mellofrom onbld.Checks import JStyle, Keywords, ManLint, Mapfile, SpellCheck 6386d41711SAndy Fiddamanfrom onbld.Checks import ShellLint, PkgFmt 648bcea973SRichard Lowe 658bcea973SRichard Loweclass GitError(Exception): 668bcea973SRichard Lowe pass 678bcea973SRichard Lowe 688bcea973SRichard Lowedef git(command): 698bcea973SRichard Lowe """Run a command and return a stream containing its stdout (and write its 708bcea973SRichard Lowe stderr to its stdout)""" 718bcea973SRichard Lowe 728bcea973SRichard Lowe if type(command) != list: 738bcea973SRichard Lowe command = command.split() 748bcea973SRichard Lowe 758bcea973SRichard Lowe command = ["git"] + command 768bcea973SRichard Lowe 77ff50e8e5SRichard Lowe try: 78ca13eaa5SAndy Fiddaman tmpfile = tempfile.TemporaryFile(prefix="git-nits", mode="w+b") 79ca13eaa5SAndy Fiddaman except EnvironmentError as e: 80ff50e8e5SRichard Lowe raise GitError("Could not create temporary file: %s\n" % e) 81ff50e8e5SRichard Lowe 82ff50e8e5SRichard Lowe try: 838bcea973SRichard Lowe p = subprocess.Popen(command, 84ff50e8e5SRichard Lowe stdout=tmpfile, 85380fd671SMatthew Ahrens stderr=subprocess.PIPE) 86ca13eaa5SAndy Fiddaman except OSError as e: 87709afb1dSDillon Amburgey raise GitError("could not execute %s: %s\n" % (command, e)) 888bcea973SRichard Lowe 898bcea973SRichard Lowe err = p.wait() 908bcea973SRichard Lowe if err != 0: 91380fd671SMatthew Ahrens raise GitError(p.stderr.read()) 928bcea973SRichard Lowe 93ff50e8e5SRichard Lowe tmpfile.seek(0) 94ca13eaa5SAndy Fiddaman lines = [] 95ca13eaa5SAndy Fiddaman for l in tmpfile: 96ca13eaa5SAndy Fiddaman lines.append(l.decode('utf-8', 'replace')) 97ca13eaa5SAndy Fiddaman return lines 988bcea973SRichard Lowe 998bcea973SRichard Lowedef git_root(): 1008bcea973SRichard Lowe """Return the root of the current git workspace""" 1018bcea973SRichard Lowe 1029af2fe54SBill Sommerfeld p = git('rev-parse --show-toplevel') 1039af2fe54SBill Sommerfeld dir = p[0].strip() 1048bcea973SRichard Lowe 1059af2fe54SBill Sommerfeld return os.path.abspath(dir) 1068bcea973SRichard Lowe 1078bcea973SRichard Lowedef git_branch(): 1088bcea973SRichard Lowe """Return the current git branch""" 1098bcea973SRichard Lowe 1108bcea973SRichard Lowe p = git('branch') 1118bcea973SRichard Lowe 1128bcea973SRichard Lowe for elt in p: 1138bcea973SRichard Lowe if elt[0] == '*': 1148bcea973SRichard Lowe if elt.endswith('(no branch)'): 1158bcea973SRichard Lowe return None 1168bcea973SRichard Lowe return elt.split()[1] 1178bcea973SRichard Lowe 1188bcea973SRichard Lowedef git_parent_branch(branch): 1198bcea973SRichard Lowe """Return the parent of the current git branch. 1208bcea973SRichard Lowe 1218bcea973SRichard Lowe If this branch tracks a remote branch, return the remote branch which is 1228bcea973SRichard Lowe tracked. If not, default to origin/master.""" 1238bcea973SRichard Lowe 1248bcea973SRichard Lowe if not branch: 1258bcea973SRichard Lowe return None 1268bcea973SRichard Lowe 12728e2b3adSHans Rosenfeld p = git(["for-each-ref", "--format=%(refname:short) %(upstream:short)", 12828e2b3adSHans Rosenfeld "refs/heads/"]) 1298bcea973SRichard Lowe 1308bcea973SRichard Lowe if not p: 1318bcea973SRichard Lowe sys.stderr.write("Failed finding git parent branch\n") 132972282a0SJohn Levon sys.exit(1) 1338bcea973SRichard Lowe 1348bcea973SRichard Lowe for line in p: 1358bcea973SRichard Lowe # Git 1.7 will leave a ' ' trailing any non-tracking branch 1368bcea973SRichard Lowe if ' ' in line and not line.endswith(' \n'): 1378bcea973SRichard Lowe local, remote = line.split() 1388bcea973SRichard Lowe if local == branch: 1398bcea973SRichard Lowe return remote 1408bcea973SRichard Lowe return 'origin/master' 1418bcea973SRichard Lowe 1423f8945a7SBill Sommerfelddef slices(strlist, sep): 1433f8945a7SBill Sommerfeld """Yield start & end of each commit within the list of comments""" 1443f8945a7SBill Sommerfeld low = 0 1453f8945a7SBill Sommerfeld for i, v in enumerate(strlist): 1463f8945a7SBill Sommerfeld if v == sep: 1473f8945a7SBill Sommerfeld yield(low, i) 1483f8945a7SBill Sommerfeld low = i+1 1493f8945a7SBill Sommerfeld 1503f8945a7SBill Sommerfeld if low != len(strlist): 1513f8945a7SBill Sommerfeld yield(low, len(strlist)) 1523f8945a7SBill Sommerfeld 1538bcea973SRichard Lowedef git_comments(parent): 1543f8945a7SBill Sommerfeld """Return the checkin comments for each commit on this git branch, 1553f8945a7SBill Sommerfeld structured as a list of lists of lines.""" 1568bcea973SRichard Lowe 15727495383SRichard Lowe p = git('log --pretty=tformat:%%B:SEP: %s..' % parent) 1588bcea973SRichard Lowe 1598bcea973SRichard Lowe if not p: 160972282a0SJohn Levon sys.stderr.write("No outgoing changesets found - missing -p option?\n"); 161972282a0SJohn Levon sys.exit(1) 1628bcea973SRichard Lowe 1633f8945a7SBill Sommerfeld return [ [line.strip() for line in p[a:b]] 1643f8945a7SBill Sommerfeld for (a, b) in slices(p, ':SEP:\n')] 1658bcea973SRichard Lowe 1668bcea973SRichard Lowedef git_file_list(parent, paths=None): 1678bcea973SRichard Lowe """Return the set of files which have ever changed on this branch. 1688bcea973SRichard Lowe 1698bcea973SRichard Lowe NB: This includes files which no longer exist, or no longer actually 1708bcea973SRichard Lowe differ.""" 1718bcea973SRichard Lowe 1728bcea973SRichard Lowe p = git("log --name-only --pretty=format: %s.. %s" % 1738bcea973SRichard Lowe (parent, ' '.join(paths))) 1748bcea973SRichard Lowe 1758bcea973SRichard Lowe if not p: 1768bcea973SRichard Lowe sys.stderr.write("Failed building file-list from git\n") 177972282a0SJohn Levon sys.exit(1) 1788bcea973SRichard Lowe 1798bcea973SRichard Lowe ret = set() 1808bcea973SRichard Lowe for fname in p: 181da88d39fSBill Sommerfeld fname = fname.strip() 182da88d39fSBill Sommerfeld if fname and not fname.isspace(): 183da88d39fSBill Sommerfeld ret.add(fname) 1848bcea973SRichard Lowe 185da88d39fSBill Sommerfeld return sorted(ret) 1868bcea973SRichard Lowe 1878bcea973SRichard Lowedef not_check(root, cmd): 1888bcea973SRichard Lowe """Return a function which returns True if a file given as an argument 1898bcea973SRichard Lowe should be excluded from the check named by 'cmd'""" 1908bcea973SRichard Lowe 191ca13eaa5SAndy Fiddaman ignorefiles = list(filter(os.path.exists, 192c82c4676SGordon Ross [os.path.join(root, ".git/info", "%s.NOT" % cmd), 193ca13eaa5SAndy Fiddaman os.path.join(root, "exception_lists", cmd)])) 194e5587435SJoshua M. Clulow return Ignore.ignore(root, ignorefiles) 1958bcea973SRichard Lowe 196955eb5e1SGarrett D'Amoredef gen_files(root, parent, paths, exclude, filter=None): 1978bcea973SRichard Lowe """Return a function producing file names, relative to the current 1988bcea973SRichard Lowe directory, of any file changed on this branch (limited to 'paths' if 1998bcea973SRichard Lowe requested), and excluding files for which exclude returns a true value """ 2008bcea973SRichard Lowe 201955eb5e1SGarrett D'Amore if filter is None: 202955eb5e1SGarrett D'Amore filter = lambda x: os.path.isfile(x) 203955eb5e1SGarrett D'Amore 2048bcea973SRichard Lowe def ret(select=None): 2058bcea973SRichard Lowe if not select: 2068bcea973SRichard Lowe select = lambda x: True 2078bcea973SRichard Lowe 20838e36c53SJohn Levon for abspath in git_file_list(parent, paths): 2099af2fe54SBill Sommerfeld path = os.path.relpath(os.path.join(root, abspath), '.') 21093d2a904SPaul Dagnelie try: 21138e36c53SJohn Levon res = git("diff %s HEAD %s" % (parent, path)) 212ca13eaa5SAndy Fiddaman except GitError as e: 21338e36c53SJohn Levon # This ignores all the errors that can be thrown. Usually, this 21438e36c53SJohn Levon # means that git returned non-zero because the file doesn't 21538e36c53SJohn Levon # exist, but it could also fail if git can't create a new file 21638e36c53SJohn Levon # or it can't be executed. Such errors are 1) unlikely, and 2) 21738e36c53SJohn Levon # will be caught by other invocations of git(). 21893d2a904SPaul Dagnelie continue 219ca13eaa5SAndy Fiddaman empty = not res 220955eb5e1SGarrett D'Amore if (filter(path) and not empty and 22138e36c53SJohn Levon select(path) and not exclude(abspath)): 22238e36c53SJohn Levon yield path 2238bcea973SRichard Lowe return ret 2248bcea973SRichard Lowe 225955eb5e1SGarrett D'Amoredef gen_links(root, parent, paths, exclude): 226955eb5e1SGarrett D'Amore """Return a function producing symbolic link names, relative to the current 227955eb5e1SGarrett D'Amore directory, of any file changed on this branch (limited to 'paths' if 228955eb5e1SGarrett D'Amore requested), and excluding files for which exclude returns a true value """ 229955eb5e1SGarrett D'Amore 230955eb5e1SGarrett D'Amore return gen_files(root, parent, paths, exclude, lambda x: os.path.islink(x)) 231955eb5e1SGarrett D'Amore 23228e88f55SBill Sommerfelddef gen_none(root, parent, paths, exclude): 23328e88f55SBill Sommerfeld """ Return a function returning the empty list """ 23428e88f55SBill Sommerfeld return lambda x: [] 23528e88f55SBill Sommerfeld 23628e88f55SBill Sommerfeld# The list of possible checks. Each is recorded as two-function pair; the 23728e88f55SBill Sommerfeld# first is the actual checker, and the second is the generator which creates 23828e88f55SBill Sommerfeld# the list of things that the checker works on. 23928e88f55SBill Sommerfeld 24028e88f55SBill Sommerfeldchecks = {} 24128e88f55SBill Sommerfeldnits_checks = [] 24228e88f55SBill Sommerfeldall_checks = [] 24328e88f55SBill Sommerfeld 24428e88f55SBill Sommerfelddef add_check(fn, gen): 24528e88f55SBill Sommerfeld """ Define a checker and add it to the appropriate lists """ 24628e88f55SBill Sommerfeld name = fn.__name__ 24728e88f55SBill Sommerfeld if fn.__doc__ is None: 24828e88f55SBill Sommerfeld raise ValueError('Check function lacks a documentation string', 24928e88f55SBill Sommerfeld name) 25028e88f55SBill Sommerfeld checks[name] = (fn, gen) 25128e88f55SBill Sommerfeld all_checks.append(name) 25228e88f55SBill Sommerfeld if gen != gen_none: 25328e88f55SBill Sommerfeld nits_checks.append(name) 25428e88f55SBill Sommerfeld return fn 25528e88f55SBill Sommerfeld 25628e88f55SBill Sommerfelddef filechecker(fn): 25728e88f55SBill Sommerfeld """ Decorator which identifies a function as being a file-checker """ 25828e88f55SBill Sommerfeld return add_check(fn, gen_files) 25928e88f55SBill Sommerfeld 26028e88f55SBill Sommerfelddef linkchecker(fn): 26128e88f55SBill Sommerfeld """ Decorator which identifies a function as being a symlink-checker """ 26228e88f55SBill Sommerfeld return add_check(fn, gen_links) 26328e88f55SBill Sommerfeld 26428e88f55SBill Sommerfelddef wschecker(fn): 26528e88f55SBill Sommerfeld """ Decorator which identifies a function as being a workspace checker """ 26628e88f55SBill Sommerfeld return add_check(fn, gen_none) 26728e88f55SBill Sommerfeld 26828e88f55SBill Sommerfeld@wschecker 2698bcea973SRichard Lowedef comchk(root, parent, flist, output): 27028e88f55SBill Sommerfeld "Check that putback comments follow the prescribed format" 2718bcea973SRichard Lowe output.write("Comments:\n") 2728bcea973SRichard Lowe 2738d226a82SBill Sommerfeld comments = git_comments(parent) 2743f8945a7SBill Sommerfeld multi = len(comments) > 1 2753f8945a7SBill Sommerfeld state = {} 2768d226a82SBill Sommerfeld 2773f8945a7SBill Sommerfeld ret = 0 2783f8945a7SBill Sommerfeld for commit in comments: 2798bcea973SRichard Lowe 2803f8945a7SBill Sommerfeld s = StringIO() 2813f8945a7SBill Sommerfeld 2823f8945a7SBill Sommerfeld result = Comments.comchk(commit, check_db=True, 2833f8945a7SBill Sommerfeld output=s, bugs=state) 2843f8945a7SBill Sommerfeld ret |= result 2853f8945a7SBill Sommerfeld 2863f8945a7SBill Sommerfeld if result != 0: 2873f8945a7SBill Sommerfeld if multi: 2883f8945a7SBill Sommerfeld output.write('\n%s\n' % commit[0]) 2893f8945a7SBill Sommerfeld output.write(s.getvalue()) 2903f8945a7SBill Sommerfeld 2913f8945a7SBill Sommerfeld return ret 2928bcea973SRichard Lowe 29328e88f55SBill Sommerfeld@filechecker 29428e88f55SBill Sommerfelddef copyright(root, parent, flist, output): 29528e88f55SBill Sommerfeld """Check that each source file contains a copyright notice for the current 29628e88f55SBill Sommerfeldyear. You don't need to fix this if you, the potential new copyright holder, 29728e88f55SBill Sommerfeldchooses not to.""" 2988bcea973SRichard Lowe ret = 0 29928e88f55SBill Sommerfeld output.write("Copyrights:\n") 30028e88f55SBill Sommerfeld for f in flist(): 30128e88f55SBill Sommerfeld with io.open(f, encoding='utf-8', errors='replace') as fh: 30228e88f55SBill Sommerfeld ret |= Copyright.copyright(fh, output=output) 30328e88f55SBill Sommerfeld return ret 3048bcea973SRichard Lowe 30528e88f55SBill Sommerfeld@filechecker 30628e88f55SBill Sommerfelddef cstyle(root, parent, flist, output): 30728e88f55SBill Sommerfeld "Check that C source files conform to the illumos C style rules" 30828e88f55SBill Sommerfeld ret = 0 30928e88f55SBill Sommerfeld output.write("C style:\n") 31028e88f55SBill Sommerfeld for f in flist(lambda x: x.endswith('.c') or x.endswith('.h')): 31128e88f55SBill Sommerfeld with io.open(f, mode='rb') as fh: 31228e88f55SBill Sommerfeld ret |= CStyle.cstyle(fh, output=output, picky=True, 31328e88f55SBill Sommerfeld check_posix_types=True, 31428e88f55SBill Sommerfeld check_continuation=True) 31528e88f55SBill Sommerfeld return ret 31628e88f55SBill Sommerfeld 31728e88f55SBill Sommerfeld@filechecker 31828e88f55SBill Sommerfelddef hdrchk(root, parent, flist, output): 31928e88f55SBill Sommerfeld "Check that C header files conform to the illumos header style rules" 32028e88f55SBill Sommerfeld ret = 0 32128e88f55SBill Sommerfeld output.write("Header format:\n") 32228e88f55SBill Sommerfeld for f in flist(lambda x: x.endswith('.h')): 32328e88f55SBill Sommerfeld with io.open(f, encoding='utf-8', errors='replace') as fh: 32428e88f55SBill Sommerfeld ret |= HdrChk.hdrchk(fh, lenient=True, output=output) 32528e88f55SBill Sommerfeld return ret 32628e88f55SBill Sommerfeld 32728e88f55SBill Sommerfeld@filechecker 32828e88f55SBill Sommerfelddef jstyle(root, parent, flist, output): 32928e88f55SBill Sommerfeld """Check that Java source files conform to the illumos Java style rules 33028e88f55SBill Sommerfeld(which differ from the traditionally recommended Java style)""" 33128e88f55SBill Sommerfeld 33228e88f55SBill Sommerfeld ret = 0 33328e88f55SBill Sommerfeld output.write("Java style:\n") 33428e88f55SBill Sommerfeld for f in flist(lambda x: x.endswith('.java')): 33528e88f55SBill Sommerfeld with io.open(f, mode='rb') as fh: 33628e88f55SBill Sommerfeld ret |= JStyle.jstyle(fh, output=output, picky=True) 33728e88f55SBill Sommerfeld return ret 33828e88f55SBill Sommerfeld 33928e88f55SBill Sommerfeld@filechecker 34028e88f55SBill Sommerfelddef keywords(root, parent, flist, output): 34128e88f55SBill Sommerfeld """Check that no source files contain unexpanded SCCS keywords. 34228e88f55SBill SommerfeldIt is possible that this check may false positive on certain inputs. 34328e88f55SBill SommerfeldIt is generally obvious when this is the case. 34428e88f55SBill Sommerfeld 34528e88f55SBill SommerfeldThis check does not check for expanded SCCS keywords, though the common 34628e88f55SBill Sommerfeld'ident'-style lines should be removed regardless of whether they are 34728e88f55SBill Sommerfeldexpanded.""" 34828e88f55SBill Sommerfeld 34928e88f55SBill Sommerfeld ret = 0 35028e88f55SBill Sommerfeld output.write("SCCS Keywords:\n") 35128e88f55SBill Sommerfeld for f in flist(): 35228e88f55SBill Sommerfeld with io.open(f, encoding='utf-8', errors='replace') as fh: 35328e88f55SBill Sommerfeld ret |= Keywords.keywords(fh, output=output) 35428e88f55SBill Sommerfeld return ret 35528e88f55SBill Sommerfeld 35628e88f55SBill Sommerfeld@filechecker 35728e88f55SBill Sommerfelddef manlint(root, parent, flist, output): 35828e88f55SBill Sommerfeld "Check for problems with man pages." 35928e88f55SBill Sommerfeld 36028e88f55SBill Sommerfeld ret = 0 36128e88f55SBill Sommerfeld output.write("Man page format/spelling:\n") 36228e88f55SBill Sommerfeld ManfileRE = re.compile(r'.*\.[0-9][a-z]*$', re.IGNORECASE) 36328e88f55SBill Sommerfeld for f in flist(lambda x: ManfileRE.match(x)): 36428e88f55SBill Sommerfeld with io.open(f, mode='rb') as fh: 36528e88f55SBill Sommerfeld ret |= ManLint.manlint(fh, output=output, picky=True) 36628e88f55SBill Sommerfeld ret |= SpellCheck.spellcheck(fh, output=output) 36728e88f55SBill Sommerfeld return ret 36828e88f55SBill Sommerfeld 36928e88f55SBill Sommerfeld@filechecker 37028e88f55SBill Sommerfelddef mapfilechk(root, parent, flist, output): 37128e88f55SBill Sommerfeld """Check that linker mapfiles contain a comment directing anyone 37228e88f55SBill Sommerfeldediting to read the directions in usr/lib/README.mapfiles.""" 37328e88f55SBill Sommerfeld 37428e88f55SBill Sommerfeld ret = 0 3758bcea973SRichard Lowe # We are interested in examining any file that has the following 3768bcea973SRichard Lowe # in its final path segment: 3778bcea973SRichard Lowe # - Contains the word 'mapfile' 3788bcea973SRichard Lowe # - Begins with 'map.' 3798bcea973SRichard Lowe # - Ends with '.map' 3808bcea973SRichard Lowe # We don't want to match unless these things occur in final path segment 3818bcea973SRichard Lowe # because directory names with these strings don't indicate a mapfile. 3828bcea973SRichard Lowe # We also ignore files with suffixes that tell us that the files 3838bcea973SRichard Lowe # are not mapfiles. 3848bcea973SRichard Lowe MapfileRE = re.compile(r'.*((mapfile[^/]*)|(/map\.+[^/]*)|(\.map))$', 3858bcea973SRichard Lowe re.IGNORECASE) 3868bcea973SRichard Lowe NotMapSuffixRE = re.compile(r'.*\.[ch]$', re.IGNORECASE) 3878bcea973SRichard Lowe 3888bcea973SRichard Lowe output.write("Mapfile comments:\n") 3898bcea973SRichard Lowe 3908bcea973SRichard Lowe for f in flist(lambda x: MapfileRE.match(x) and not 3918bcea973SRichard Lowe NotMapSuffixRE.match(x)): 392ca13eaa5SAndy Fiddaman with io.open(f, encoding='utf-8', errors='replace') as fh: 3938bcea973SRichard Lowe ret |= Mapfile.mapfilechk(fh, output=output) 3948bcea973SRichard Lowe return ret 3958bcea973SRichard Lowe 39628e88f55SBill Sommerfeld@filechecker 39713904da8SAndy Fiddamandef shelllint(root, parent, flist, output): 39828e88f55SBill Sommerfeld """Check shell scripts for common errors.""" 39913904da8SAndy Fiddaman ret = 0 40013904da8SAndy Fiddaman output.write("Shell lint:\n") 40113904da8SAndy Fiddaman 40213904da8SAndy Fiddaman def isshell(x): 40313904da8SAndy Fiddaman (_, ext) = os.path.splitext(x) 40413904da8SAndy Fiddaman if ext in ['.sh', '.ksh']: 40513904da8SAndy Fiddaman return True 40613904da8SAndy Fiddaman if ext == '': 40713904da8SAndy Fiddaman with io.open(x, mode='r', errors='ignore') as fh: 40813904da8SAndy Fiddaman if re.match(r'^#.*\bk?sh\b', fh.readline()): 40913904da8SAndy Fiddaman return True 41013904da8SAndy Fiddaman return False 41113904da8SAndy Fiddaman 41213904da8SAndy Fiddaman for f in flist(isshell): 41313904da8SAndy Fiddaman with io.open(f, mode='rb') as fh: 41413904da8SAndy Fiddaman ret |= ShellLint.lint(fh, output=output) 41513904da8SAndy Fiddaman 41613904da8SAndy Fiddaman return ret 41713904da8SAndy Fiddaman 41828e88f55SBill Sommerfeld@filechecker 41986d41711SAndy Fiddamandef pkgfmt(root, parent, flist, output): 42028e88f55SBill Sommerfeld """Check package manifests for common errors.""" 42186d41711SAndy Fiddaman ret = 0 42286d41711SAndy Fiddaman output.write("Package manifests:\n") 42386d41711SAndy Fiddaman 42425b05a3eSAndy Fiddaman for f in flist(lambda x: x.endswith('.p5m')): 42586d41711SAndy Fiddaman with io.open(f, mode='rb') as fh: 42686d41711SAndy Fiddaman ret |= PkgFmt.check(fh, output=output) 42786d41711SAndy Fiddaman 42886d41711SAndy Fiddaman return ret 42986d41711SAndy Fiddaman 430955eb5e1SGarrett D'Amoredef iswinreserved(name): 431955eb5e1SGarrett D'Amore reserved = [ 432955eb5e1SGarrett D'Amore 'con', 'prn', 'aux', 'nul', 433955eb5e1SGarrett D'Amore 'com1', 'com2', 'com3', 'com4', 'com5', 434955eb5e1SGarrett D'Amore 'com6', 'com7', 'com8', 'com9', 'com0', 435955eb5e1SGarrett D'Amore 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 436955eb5e1SGarrett D'Amore 'lpt6', 'lpt7', 'lpt8', 'lpt9', 'lpt0' ] 437955eb5e1SGarrett D'Amore l = name.lower() 438955eb5e1SGarrett D'Amore for r in reserved: 439955eb5e1SGarrett D'Amore if l == r or l.startswith(r+"."): 440955eb5e1SGarrett D'Amore return True 441955eb5e1SGarrett D'Amore return False 442955eb5e1SGarrett D'Amore 443955eb5e1SGarrett D'Amoredef haswinspecial(name): 444955eb5e1SGarrett D'Amore specials = '<>:"\\|?*' 445955eb5e1SGarrett D'Amore for c in name: 446955eb5e1SGarrett D'Amore if c in specials: 447955eb5e1SGarrett D'Amore return True 448955eb5e1SGarrett D'Amore return False 449955eb5e1SGarrett D'Amore 45028e88f55SBill Sommerfeld@filechecker 451955eb5e1SGarrett D'Amoredef winnames(root, parent, flist, output): 45228e88f55SBill Sommerfeld "Check for filenames which can't be used in a Windows filesystem." 453955eb5e1SGarrett D'Amore ret = 0 454955eb5e1SGarrett D'Amore output.write("Illegal filenames (Windows):\n") 455955eb5e1SGarrett D'Amore for f in flist(): 456955eb5e1SGarrett D'Amore if haswinspecial(f): 457955eb5e1SGarrett D'Amore output.write(" "+f+": invalid character in name\n") 458955eb5e1SGarrett D'Amore ret |= 1 459955eb5e1SGarrett D'Amore continue 460955eb5e1SGarrett D'Amore 461955eb5e1SGarrett D'Amore parts = f.split('/') 462955eb5e1SGarrett D'Amore for p in parts: 463955eb5e1SGarrett D'Amore if iswinreserved(p): 464955eb5e1SGarrett D'Amore output.write(" "+f+": reserved file name\n") 465955eb5e1SGarrett D'Amore ret |= 1 466955eb5e1SGarrett D'Amore break 467955eb5e1SGarrett D'Amore 468955eb5e1SGarrett D'Amore return ret 469955eb5e1SGarrett D'Amore 47028e88f55SBill Sommerfeld@filechecker 47128e88f55SBill Sommerfelddef wscheck(root, parent, flist, output): 47228e88f55SBill Sommerfeld "Check for whitespace issues such as mixed tabs/spaces in source files." 47328e88f55SBill Sommerfeld ret = 0 47428e88f55SBill Sommerfeld output.write("white space nits:\n") 47528e88f55SBill Sommerfeld for f in flist(): 47628e88f55SBill Sommerfeld with io.open(f, encoding='utf-8', errors='replace') as fh: 47728e88f55SBill Sommerfeld ret |= WsCheck.wscheck(fh, output=output) 47828e88f55SBill Sommerfeld return ret 47928e88f55SBill Sommerfeld 48028e88f55SBill Sommerfeld@linkchecker 48128e88f55SBill Sommerfelddef symlinks(root, parent, flist, output): 48228e88f55SBill Sommerfeld "Check for committed symlinks (there shouldn't be any)." 48328e88f55SBill Sommerfeld ret = 0 48428e88f55SBill Sommerfeld output.write("Symbolic links:\n") 48528e88f55SBill Sommerfeld for f in flist(): 48628e88f55SBill Sommerfeld output.write(" "+f+"\n") 48728e88f55SBill Sommerfeld ret |= 1 48828e88f55SBill Sommerfeld return ret 48928e88f55SBill Sommerfeld 49028e88f55SBill Sommerfelddef run_checks(root, parent, checklist, paths=''): 49128e88f55SBill Sommerfeld """Run the checks named in 'checklist', 4928bcea973SRichard Lowe and report results for any which fail. 4938bcea973SRichard Lowe 4948bcea973SRichard Lowe Return failure if any of them did. 4958bcea973SRichard Lowe 49628e88f55SBill Sommerfeld NB: the check names also name the NOT 4978bcea973SRichard Lowe file which excepts files from them.""" 4988bcea973SRichard Lowe 4998bcea973SRichard Lowe ret = 0 5008bcea973SRichard Lowe 50128e88f55SBill Sommerfeld for check in checklist: 50228e88f55SBill Sommerfeld (cmd, gen) = checks[check] 50328e88f55SBill Sommerfeld 5048bcea973SRichard Lowe s = StringIO() 5058bcea973SRichard Lowe 50628e88f55SBill Sommerfeld exclude = not_check(root, check) 50728e88f55SBill Sommerfeld result = cmd(root, parent, gen(root, parent, paths, exclude), 508955eb5e1SGarrett D'Amore output=s) 509955eb5e1SGarrett D'Amore ret |= result 510955eb5e1SGarrett D'Amore 511955eb5e1SGarrett D'Amore if result != 0: 512955eb5e1SGarrett D'Amore print(s.getvalue()) 513955eb5e1SGarrett D'Amore 5148bcea973SRichard Lowe return ret 5158bcea973SRichard Lowe 51628e88f55SBill Sommerfelddef print_checks(): 5178bcea973SRichard Lowe 51828e88f55SBill Sommerfeld for c in all_checks: 51928e88f55SBill Sommerfeld print(textwrap.fill( 52028e88f55SBill Sommerfeld "%-11s %s" % (c, checks[c][0].__doc__), 52128e88f55SBill Sommerfeld width=78, 52228e88f55SBill Sommerfeld subsequent_indent=' '*12), '\n') 5238bcea973SRichard Lowe 5248bcea973SRichard Lowedef main(cmd, args): 5258bcea973SRichard Lowe parent_branch = None 526*cbbc171dSiximeow allow_questionable_requests = False 52728e88f55SBill Sommerfeld 52828e88f55SBill Sommerfeld checklist = [] 5298bcea973SRichard Lowe 5308bcea973SRichard Lowe try: 531*cbbc171dSiximeow opts, args = getopt.getopt(args, 'lfb:c:p:') 532ca13eaa5SAndy Fiddaman except getopt.GetoptError as e: 5338bcea973SRichard Lowe sys.stderr.write(str(e) + '\n') 534*cbbc171dSiximeow sys.stderr.write("Usage: %s [-l] [-f] [-c check] [-p branch] " 535*cbbc171dSiximeow "[path...]\n" % cmd) 5368bcea973SRichard Lowe sys.exit(1) 5378bcea973SRichard Lowe 5388bcea973SRichard Lowe for opt, arg in opts: 53928e88f55SBill Sommerfeld if opt == '-l': 54028e88f55SBill Sommerfeld print_checks() 54128e88f55SBill Sommerfeld sys.exit(0) 542*cbbc171dSiximeow elif opt == '-f': 543*cbbc171dSiximeow allow_questionable_requests = True 54442a3762dSJoshua M. Clulow # We accept "-b" as an alias of "-p" for backwards compatibility. 54528e88f55SBill Sommerfeld elif opt == '-p' or opt == '-b': 5468bcea973SRichard Lowe parent_branch = arg 547eabe844aSJohn Levon elif opt == '-c': 54828e88f55SBill Sommerfeld if arg not in checks: 54928e88f55SBill Sommerfeld sys.stderr.write("Unknown check '%s'\n" % arg) 55028e88f55SBill Sommerfeld sys.exit(1) 55128e88f55SBill Sommerfeld checklist.append(arg) 5528bcea973SRichard Lowe 5538bcea973SRichard Lowe if not parent_branch: 5548bcea973SRichard Lowe parent_branch = git_parent_branch(git_branch()) 5558bcea973SRichard Lowe 556*cbbc171dSiximeow comments = git_comments(parent_branch) 557*cbbc171dSiximeow if len(comments) > 5 and not allow_questionable_requests: 558*cbbc171dSiximeow sys.stderr.write("Declining to check history since %s, would be %d " 559*cbbc171dSiximeow "commits. Rerun with -f if you really mean to.\n" % 560*cbbc171dSiximeow (parent_branch, len(comments))) 561*cbbc171dSiximeow sys.exit(1) 562*cbbc171dSiximeow 56328e88f55SBill Sommerfeld if len(checklist) == 0: 5648bcea973SRichard Lowe if cmd == 'git-pbchk': 5658bcea973SRichard Lowe if args: 5668bcea973SRichard Lowe sys.stderr.write("only complete workspaces may be pbchk'd\n"); 5678bcea973SRichard Lowe sys.exit(1) 56828e88f55SBill Sommerfeld checklist = all_checks 569eabe844aSJohn Levon else: 57028e88f55SBill Sommerfeld checklist = nits_checks 57128e88f55SBill Sommerfeld 57228e88f55SBill Sommerfeld run_checks(git_root(), parent_branch, checklist, args) 5738bcea973SRichard Lowe 5748bcea973SRichard Loweif __name__ == '__main__': 5758bcea973SRichard Lowe try: 5768bcea973SRichard Lowe main(os.path.basename(sys.argv[0]), sys.argv[1:]) 577ca13eaa5SAndy Fiddaman except GitError as e: 5788bcea973SRichard Lowe sys.stderr.write("failed to run git:\n %s\n" % str(e)) 5798bcea973SRichard Lowe sys.exit(1) 580