OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python2.7 |
| 2 |
| 3 # Copyright 2015-2016, Google Inc. |
| 4 # All rights reserved. |
| 5 # |
| 6 # Redistribution and use in source and binary forms, with or without |
| 7 # modification, are permitted provided that the following conditions are |
| 8 # met: |
| 9 # |
| 10 # * Redistributions of source code must retain the above copyright |
| 11 # notice, this list of conditions and the following disclaimer. |
| 12 # * Redistributions in binary form must reproduce the above |
| 13 # copyright notice, this list of conditions and the following disclaimer |
| 14 # in the documentation and/or other materials provided with the |
| 15 # distribution. |
| 16 # * Neither the name of Google Inc. nor the names of its |
| 17 # contributors may be used to endorse or promote products derived from |
| 18 # this software without specific prior written permission. |
| 19 # |
| 20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 23 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 24 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 25 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 26 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 27 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 28 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 29 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 30 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 31 |
| 32 import argparse |
| 33 import datetime |
| 34 import os |
| 35 import re |
| 36 import sys |
| 37 import subprocess |
| 38 |
| 39 # find our home |
| 40 ROOT = os.path.abspath( |
| 41 os.path.join(os.path.dirname(sys.argv[0]), '../..')) |
| 42 os.chdir(ROOT) |
| 43 |
| 44 # parse command line |
| 45 argp = argparse.ArgumentParser(description='copyright checker') |
| 46 argp.add_argument('-o', '--output', |
| 47 default='details', |
| 48 choices=['list', 'details']) |
| 49 argp.add_argument('-s', '--skips', |
| 50 default=0, |
| 51 action='store_const', |
| 52 const=1) |
| 53 argp.add_argument('-a', '--ancient', |
| 54 default=0, |
| 55 action='store_const', |
| 56 const=1) |
| 57 argp.add_argument('-f', '--fix', |
| 58 default=False, |
| 59 action='store_true'); |
| 60 argp.add_argument('--precommit', |
| 61 default=False, |
| 62 action='store_true') |
| 63 args = argp.parse_args() |
| 64 |
| 65 # open the license text |
| 66 with open('LICENSE') as f: |
| 67 LICENSE = f.read().splitlines() |
| 68 |
| 69 # license format by file extension |
| 70 # key is the file extension, value is a format string |
| 71 # that given a line of license text, returns what should |
| 72 # be in the file |
| 73 LICENSE_PREFIX = { |
| 74 '.c': r'\s*(?://|\*)\s*', |
| 75 '.cc': r'\s*(?://|\*)\s*', |
| 76 '.h': r'\s*(?://|\*)\s*', |
| 77 '.m': r'\s*\*\s*', |
| 78 '.php': r'\s*\*\s*', |
| 79 '.js': r'\s*\*\s*', |
| 80 '.py': r'#\s*', |
| 81 '.pyx': r'#\s*', |
| 82 '.pxd': r'#\s*', |
| 83 '.pxi': r'#\s*', |
| 84 '.rb': r'#\s*', |
| 85 '.sh': r'#\s*', |
| 86 '.proto': r'//\s*', |
| 87 '.cs': r'//\s*', |
| 88 '.mak': r'#\s*', |
| 89 'Makefile': r'#\s*', |
| 90 'Dockerfile': r'#\s*', |
| 91 'LICENSE': '', |
| 92 } |
| 93 |
| 94 KNOWN_BAD = set([ |
| 95 'src/php/tests/bootstrap.php', |
| 96 ]) |
| 97 |
| 98 |
| 99 RE_YEAR = r'Copyright (?P<first_year>[0-9]+\-)?(?P<last_year>[0-9]+), Google Inc
\.' |
| 100 RE_LICENSE = dict( |
| 101 (k, r'\n'.join( |
| 102 LICENSE_PREFIX[k] + |
| 103 (RE_YEAR if re.search(RE_YEAR, line) else re.escape(line)) |
| 104 for line in LICENSE)) |
| 105 for k, v in LICENSE_PREFIX.iteritems()) |
| 106 |
| 107 if args.precommit: |
| 108 FILE_LIST_COMMAND = 'git diff --name-only HEAD | grep -v ^third_party/' |
| 109 else: |
| 110 FILE_LIST_COMMAND = 'git ls-tree -r --name-only -r HEAD | grep -v ^third_party
/' |
| 111 |
| 112 def load(name): |
| 113 with open(name) as f: |
| 114 return f.read() |
| 115 |
| 116 def save(name, text): |
| 117 with open(name, 'w') as f: |
| 118 f.write(text) |
| 119 |
| 120 assert(re.search(RE_LICENSE['LICENSE'], load('LICENSE'))) |
| 121 assert(re.search(RE_LICENSE['Makefile'], load('Makefile'))) |
| 122 |
| 123 |
| 124 def log(cond, why, filename): |
| 125 if not cond: return |
| 126 if args.output == 'details': |
| 127 print '%s: %s' % (why, filename) |
| 128 else: |
| 129 print filename |
| 130 |
| 131 |
| 132 # scan files, validate the text |
| 133 ok = True |
| 134 filename_list = [] |
| 135 try: |
| 136 filename_list = subprocess.check_output(FILE_LIST_COMMAND, |
| 137 shell=True).splitlines() |
| 138 except subprocess.CalledProcessError: |
| 139 sys.exit(0) |
| 140 |
| 141 for filename in filename_list: |
| 142 if filename in KNOWN_BAD: continue |
| 143 ext = os.path.splitext(filename)[1] |
| 144 base = os.path.basename(filename) |
| 145 if ext in RE_LICENSE: |
| 146 re_license = RE_LICENSE[ext] |
| 147 elif base in RE_LICENSE: |
| 148 re_license = RE_LICENSE[base] |
| 149 else: |
| 150 log(args.skips, 'skip', filename) |
| 151 continue |
| 152 try: |
| 153 text = load(filename) |
| 154 except: |
| 155 continue |
| 156 m = re.search(re_license, text) |
| 157 if m: |
| 158 gdict = m.groupdict() |
| 159 last_modified = int(subprocess.check_output('git log -1 --format="%ad" --dat
e=short -- ' + filename, shell=True)[0:4]) |
| 160 latest_claimed = int(gdict['last_year']) |
| 161 if last_modified > latest_claimed: |
| 162 print '%s modified %d but copyright only extends to %d' % (filename, last_
modified, latest_claimed) |
| 163 ok = False |
| 164 if args.fix: |
| 165 span_start, span_end = m.span(2) |
| 166 if not gdict['first_year']: |
| 167 # prepend the old year to the current one. |
| 168 text = '{}-{}{}'.format(text[:span_end], last_modified, text[span_end:
]) |
| 169 else: # already a year range |
| 170 # simply update the last year |
| 171 text = '{}{}{}'.format(text[:span_start], last_modified, text[span_end
:]) |
| 172 save(filename, text) |
| 173 print 'Fixed!' |
| 174 ok = True |
| 175 elif 'DO NOT EDIT' not in text and 'AssemblyInfo.cs' not in filename and filen
ame != 'src/boringssl/err_data.c': |
| 176 log(1, 'copyright missing', filename) |
| 177 ok = False |
| 178 |
| 179 sys.exit(0 if ok else 1) |
OLD | NEW |