1#!/usr/bin/env python2.7 2 3# Copyright 2015 gRPC authors. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import argparse 18import datetime 19import os 20import re 21import sys 22import subprocess 23 24# find our home 25ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..')) 26os.chdir(ROOT) 27 28# parse command line 29argp = argparse.ArgumentParser(description='copyright checker') 30argp.add_argument( 31 '-o', '--output', default='details', choices=['list', 'details']) 32argp.add_argument('-s', '--skips', default=0, action='store_const', const=1) 33argp.add_argument('-a', '--ancient', default=0, action='store_const', const=1) 34argp.add_argument('--precommit', default=False, action='store_true') 35args = argp.parse_args() 36 37# open the license text 38with open('NOTICE.txt') as f: 39 LICENSE_NOTICE = f.read().splitlines() 40 41# license format by file extension 42# key is the file extension, value is a format string 43# that given a line of license text, returns what should 44# be in the file 45LICENSE_PREFIX = { 46 '.bat': r'@rem\s*', 47 '.c': r'\s*(?://|\*)\s*', 48 '.cc': r'\s*(?://|\*)\s*', 49 '.h': r'\s*(?://|\*)\s*', 50 '.m': r'\s*\*\s*', 51 '.mm': r'\s*\*\s*', 52 '.php': r'\s*\*\s*', 53 '.js': r'\s*\*\s*', 54 '.py': r'#\s*', 55 '.pyx': r'#\s*', 56 '.pxd': r'#\s*', 57 '.pxi': r'#\s*', 58 '.rb': r'#\s*', 59 '.sh': r'#\s*', 60 '.proto': r'//\s*', 61 '.cs': r'//\s*', 62 '.mak': r'#\s*', 63 'Makefile': r'#\s*', 64 'Dockerfile': r'#\s*', 65 'BUILD': r'#\s*', 66} 67 68_EXEMPT = frozenset(( 69 # Generated protocol compiler output. 70 'examples/python/helloworld/helloworld_pb2.py', 71 'examples/python/helloworld/helloworld_pb2_grpc.py', 72 'examples/python/multiplex/helloworld_pb2.py', 73 'examples/python/multiplex/helloworld_pb2_grpc.py', 74 'examples/python/multiplex/route_guide_pb2.py', 75 'examples/python/multiplex/route_guide_pb2_grpc.py', 76 'examples/python/route_guide/route_guide_pb2.py', 77 'examples/python/route_guide/route_guide_pb2_grpc.py', 78 'src/core/ext/filters/client_channel/lb_policy/grpclb/proto/grpc/lb/v1/load_balancer.pb.h', 79 'src/core/ext/filters/client_channel/lb_policy/grpclb/proto/grpc/lb/v1/load_balancer.pb.c', 80 'src/core/ext/filters/client_channel/lb_policy/grpclb/proto/grpc/lb/v1/google/protobuf/duration.pb.h', 81 'src/core/ext/filters/client_channel/lb_policy/grpclb/proto/grpc/lb/v1/google/protobuf/duration.pb.c', 82 'src/core/ext/filters/client_channel/lb_policy/grpclb/proto/grpc/lb/v1/google/protobuf/timestamp.pb.h', 83 'src/core/ext/filters/client_channel/lb_policy/grpclb/proto/grpc/lb/v1/google/protobuf/timestamp.pb.c', 84 'src/core/tsi/alts/handshaker/altscontext.pb.h', 85 'src/core/tsi/alts/handshaker/altscontext.pb.c', 86 'src/core/tsi/alts/handshaker/handshaker.pb.h', 87 'src/core/tsi/alts/handshaker/handshaker.pb.c', 88 'src/core/tsi/alts/handshaker/transport_security_common.pb.h', 89 'src/core/tsi/alts/handshaker/transport_security_common.pb.c', 90 'src/cpp/server/health/health.pb.h', 91 'src/cpp/server/health/health.pb.c', 92 93 # An older file originally from outside gRPC. 94 'src/php/tests/bootstrap.php', 95 # census.proto copied from github 96 'tools/grpcz/census.proto', 97 # status.proto copied from googleapis 98 'src/proto/grpc/status/status.proto', 99 100 # Gradle wrappers used to build for Android 101 'examples/android/helloworld/gradlew.bat', 102 'src/android/test/interop/gradlew.bat', 103 104 # Designer-generated source 105 'examples/csharp/HelloworldXamarin/Droid/Resources/Resource.designer.cs', 106 'examples/csharp/HelloworldXamarin/iOS/ViewController.designer.cs', 107)) 108 109RE_YEAR = r'Copyright (?P<first_year>[0-9]+\-)?(?P<last_year>[0-9]+) ([Tt]he )?gRPC [Aa]uthors(\.|)' 110RE_LICENSE = dict( 111 (k, r'\n'.join(LICENSE_PREFIX[k] + 112 (RE_YEAR if re.search(RE_YEAR, line) else re.escape(line)) 113 for line in LICENSE_NOTICE)) 114 for k, v in LICENSE_PREFIX.iteritems()) 115 116if args.precommit: 117 FILE_LIST_COMMAND = 'git status -z | grep -Poz \'(?<=^[MARC][MARCD ] )[^\s]+\'' 118else: 119 FILE_LIST_COMMAND = 'git ls-tree -r --name-only -r HEAD | ' \ 120 'grep -v ^third_party/ |' \ 121 'grep -v "\(ares_config.h\|ares_build.h\)"' 122 123 124def load(name): 125 with open(name) as f: 126 return f.read() 127 128 129def save(name, text): 130 with open(name, 'w') as f: 131 f.write(text) 132 133 134assert (re.search(RE_LICENSE['Makefile'], load('Makefile'))) 135 136 137def log(cond, why, filename): 138 if not cond: return 139 if args.output == 'details': 140 print '%s: %s' % (why, filename) 141 else: 142 print filename 143 144 145# scan files, validate the text 146ok = True 147filename_list = [] 148try: 149 filename_list = subprocess.check_output( 150 FILE_LIST_COMMAND, shell=True).splitlines() 151except subprocess.CalledProcessError: 152 sys.exit(0) 153 154for filename in filename_list: 155 if filename in _EXEMPT: 156 continue 157 ext = os.path.splitext(filename)[1] 158 base = os.path.basename(filename) 159 if ext in RE_LICENSE: 160 re_license = RE_LICENSE[ext] 161 elif base in RE_LICENSE: 162 re_license = RE_LICENSE[base] 163 else: 164 log(args.skips, 'skip', filename) 165 continue 166 try: 167 text = load(filename) 168 except: 169 continue 170 m = re.search(re_license, text) 171 if m: 172 pass 173 elif 'DO NOT EDIT' not in text and filename not in [ 174 'src/boringssl/err_data.c', 'src/boringssl/crypto_test_data.cc' 175 ]: 176 log(1, 'copyright missing', filename) 177 ok = False 178 179sys.exit(0 if ok else 1) 180