blob: 4577ab3d11d4543e8fa89d502b5177735954ab7c [file] [log] [blame]
Nathaniel Manistaae4fbcd2015-09-23 16:29:44 +00001#!/usr/bin/env python2.7
Craig Tillerc2c79212015-02-16 12:00:01 -08002
Craig Tiller6e2cf6b2016-03-31 07:46:10 -07003# Copyright 2015, Google Inc.
Craig Tillerc2c79212015-02-16 12:00:01 -08004# 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
Craig Tiller94640a32015-02-17 20:42:05 -080032import argparse
Craig Tillerde3da742016-01-04 14:41:25 -080033import datetime
Craig Tiller1a61b172015-02-16 11:53:47 -080034import os
Craig Tillerde3da742016-01-04 14:41:25 -080035import re
Craig Tiller1a61b172015-02-16 11:53:47 -080036import sys
37import subprocess
38
39# find our home
40ROOT = os.path.abspath(
41 os.path.join(os.path.dirname(sys.argv[0]), '../..'))
42os.chdir(ROOT)
43
Craig Tiller94640a32015-02-17 20:42:05 -080044# parse command line
45argp = argparse.ArgumentParser(description='copyright checker')
Craig Tiller06059952015-02-18 08:34:56 -080046argp.add_argument('-o', '--output',
47 default='details',
Craig Tiller94640a32015-02-17 20:42:05 -080048 choices=['list', 'details'])
Craig Tiller06059952015-02-18 08:34:56 -080049argp.add_argument('-s', '--skips',
Craig Tiller94640a32015-02-17 20:42:05 -080050 default=0,
51 action='store_const',
52 const=1)
Craig Tiller06059952015-02-18 08:34:56 -080053argp.add_argument('-a', '--ancient',
54 default=0,
55 action='store_const',
56 const=1)
David Garcia Quintasfa587862016-01-12 15:31:40 -080057argp.add_argument('-f', '--fix',
58 default=False,
59 action='store_true');
murgatroid99f656f182016-02-09 10:56:25 -080060argp.add_argument('--precommit',
61 default=False,
62 action='store_true')
Craig Tiller94640a32015-02-17 20:42:05 -080063args = argp.parse_args()
64
Craig Tiller1a61b172015-02-16 11:53:47 -080065# open the license text
66with 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
Craig Tillerde3da742016-01-04 14:41:25 -080073LICENSE_PREFIX = {
Jan Tattermuschfdb89312016-05-13 10:38:34 -070074 '.bat': r'@rem\s*',
Craig Tiller2d50f372016-01-28 11:48:18 -080075 '.c': r'\s*(?://|\*)\s*',
76 '.cc': r'\s*(?://|\*)\s*',
77 '.h': r'\s*(?://|\*)\s*',
Craig Tillerde3da742016-01-04 14:41:25 -080078 '.m': r'\s*\*\s*',
79 '.php': r'\s*\*\s*',
80 '.js': r'\s*\*\s*',
81 '.py': r'#\s*',
Craig Tillerbefa1ff2016-01-05 07:54:11 -080082 '.pyx': r'#\s*',
83 '.pxd': r'#\s*',
84 '.pxi': r'#\s*',
Craig Tillerde3da742016-01-04 14:41:25 -080085 '.rb': r'#\s*',
86 '.sh': r'#\s*',
87 '.proto': r'//\s*',
88 '.cs': r'//\s*',
89 '.mak': r'#\s*',
90 'Makefile': r'#\s*',
91 'Dockerfile': r'#\s*',
92 'LICENSE': '',
Craig Tiller1a61b172015-02-16 11:53:47 -080093}
94
Craig Tiller897e4fe2015-12-22 15:03:40 -080095KNOWN_BAD = set([
96 'src/php/tests/bootstrap.php',
97])
98
Craig Tiller1a61b172015-02-16 11:53:47 -080099
David Garcia Quintasfa587862016-01-12 15:31:40 -0800100RE_YEAR = r'Copyright (?P<first_year>[0-9]+\-)?(?P<last_year>[0-9]+), Google Inc\.'
Craig Tillerde3da742016-01-04 14:41:25 -0800101RE_LICENSE = dict(
102 (k, r'\n'.join(
103 LICENSE_PREFIX[k] +
104 (RE_YEAR if re.search(RE_YEAR, line) else re.escape(line))
105 for line in LICENSE))
106 for k, v in LICENSE_PREFIX.iteritems())
107
murgatroid99f656f182016-02-09 10:56:25 -0800108if args.precommit:
murgatroid99028bd452016-03-16 16:25:42 -0700109 FILE_LIST_COMMAND = 'git status -z | grep -Poz \'(?<=^[MARC][MARCD ] )[^\s]+\''
murgatroid99f656f182016-02-09 10:56:25 -0800110else:
111 FILE_LIST_COMMAND = 'git ls-tree -r --name-only -r HEAD | grep -v ^third_party/'
Craig Tillerde3da742016-01-04 14:41:25 -0800112
113def load(name):
114 with open(name) as f:
David Garcia Quintas68a16862016-01-14 10:56:48 -0800115 return f.read()
Craig Tillerde3da742016-01-04 14:41:25 -0800116
David Garcia Quintasfa587862016-01-12 15:31:40 -0800117def save(name, text):
118 with open(name, 'w') as f:
119 f.write(text)
Craig Tillerde3da742016-01-04 14:41:25 -0800120
121assert(re.search(RE_LICENSE['LICENSE'], load('LICENSE')))
122assert(re.search(RE_LICENSE['Makefile'], load('Makefile')))
123
Craig Tiller1a61b172015-02-16 11:53:47 -0800124
Craig Tiller94640a32015-02-17 20:42:05 -0800125def log(cond, why, filename):
126 if not cond: return
127 if args.output == 'details':
128 print '%s: %s' % (why, filename)
129 else:
130 print filename
131
Craig Tillerde3da742016-01-04 14:41:25 -0800132
Craig Tiller1a61b172015-02-16 11:53:47 -0800133# scan files, validate the text
Craig Tiller750d3ed2016-01-12 09:38:25 -0800134ok = True
murgatroid99c58a7812016-02-09 16:33:17 -0800135filename_list = []
136try:
137 filename_list = subprocess.check_output(FILE_LIST_COMMAND,
138 shell=True).splitlines()
139except subprocess.CalledProcessError:
140 sys.exit(0)
141
142for filename in filename_list:
Craig Tiller897e4fe2015-12-22 15:03:40 -0800143 if filename in KNOWN_BAD: continue
Craig Tiller1a61b172015-02-16 11:53:47 -0800144 ext = os.path.splitext(filename)[1]
Craig Tiller6ce372c2015-02-20 09:44:12 -0800145 base = os.path.basename(filename)
Craig Tillerde3da742016-01-04 14:41:25 -0800146 if ext in RE_LICENSE:
147 re_license = RE_LICENSE[ext]
148 elif base in RE_LICENSE:
149 re_license = RE_LICENSE[base]
Craig Tiller6ce372c2015-02-20 09:44:12 -0800150 else:
Craig Tiller94640a32015-02-17 20:42:05 -0800151 log(args.skips, 'skip', filename)
Craig Tillerf222a4a2015-02-16 12:28:10 -0800152 continue
Craig Tiller94d04a52016-01-20 10:58:23 -0800153 try:
154 text = load(filename)
155 except:
156 continue
Craig Tillerde3da742016-01-04 14:41:25 -0800157 m = re.search(re_license, text)
158 if m:
Craig Tiller6e2cf6b2016-03-31 07:46:10 -0700159 pass
Craig Tillerde3da742016-01-04 14:41:25 -0800160 elif 'DO NOT EDIT' not in text and 'AssemblyInfo.cs' not in filename and filename != 'src/boringssl/err_data.c':
161 log(1, 'copyright missing', filename)
162 ok = False
163
164sys.exit(0 if ok else 1)