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 17from __future__ import print_function 18import argparse 19import datetime 20import os 21import re 22import sys 23import subprocess 24 25# find our home 26ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..')) 27os.chdir(ROOT) 28 29# parse command line 30argp = argparse.ArgumentParser(description='copyright checker') 31argp.add_argument('-o', 32 '--output', 33 default='details', 34 choices=['list', 'details']) 35argp.add_argument('-s', '--skips', default=0, action='store_const', const=1) 36argp.add_argument('-a', '--ancient', default=0, action='store_const', const=1) 37argp.add_argument('--precommit', default=False, action='store_true') 38args = argp.parse_args() 39 40# open the license text 41with open('NOTICE.txt') as f: 42 LICENSE_NOTICE = f.read().splitlines() 43 44# license format by file extension 45# key is the file extension, value is a format string 46# that given a line of license text, returns what should 47# be in the file 48LICENSE_PREFIX = { 49 '.bat': r'@rem\s*', 50 '.c': r'\s*(?://|\*)\s*', 51 '.cc': r'\s*(?://|\*)\s*', 52 '.h': r'\s*(?://|\*)\s*', 53 '.m': r'\s*\*\s*', 54 '.mm': r'\s*\*\s*', 55 '.php': r'\s*\*\s*', 56 '.js': r'\s*\*\s*', 57 '.py': r'#\s*', 58 '.pyx': r'#\s*', 59 '.pxd': r'#\s*', 60 '.pxi': r'#\s*', 61 '.rb': r'#\s*', 62 '.sh': r'#\s*', 63 '.proto': r'//\s*', 64 '.cs': r'//\s*', 65 '.mak': r'#\s*', 66 'Makefile': r'#\s*', 67 'Dockerfile': r'#\s*', 68 'BUILD': r'#\s*', 69} 70 71_EXEMPT = frozenset(( 72 # Generated protocol compiler output. 73 'examples/python/helloworld/helloworld_pb2.py', 74 'examples/python/helloworld/helloworld_pb2_grpc.py', 75 'examples/python/multiplex/helloworld_pb2.py', 76 'examples/python/multiplex/helloworld_pb2_grpc.py', 77 'examples/python/multiplex/route_guide_pb2.py', 78 'examples/python/multiplex/route_guide_pb2_grpc.py', 79 'examples/python/route_guide/route_guide_pb2.py', 80 'examples/python/route_guide/route_guide_pb2_grpc.py', 81 82 # Generated doxygen config file 83 'tools/doxygen/Doxyfile.php', 84 85 # An older file originally from outside gRPC. 86 'src/php/tests/bootstrap.php', 87 # census.proto copied from github 88 'tools/grpcz/census.proto', 89 # status.proto copied from googleapis 90 'src/proto/grpc/status/status.proto', 91 92 # Gradle wrappers used to build for Android 93 'examples/android/helloworld/gradlew.bat', 94 'src/android/test/interop/gradlew.bat', 95 96 # Designer-generated source 97 'examples/csharp/HelloworldXamarin/Droid/Resources/Resource.designer.cs', 98 'examples/csharp/HelloworldXamarin/iOS/ViewController.designer.cs', 99 100 # BoringSSL generated header. It has commit version information at the head 101 # of the file so we cannot check the license info. 102 'src/boringssl/boringssl_prefix_symbols.h', 103)) 104 105RE_YEAR = r'Copyright (?P<first_year>[0-9]+\-)?(?P<last_year>[0-9]+) ([Tt]he )?gRPC [Aa]uthors(\.|)' 106RE_LICENSE = dict( 107 (k, r'\n'.join(LICENSE_PREFIX[k] + 108 (RE_YEAR if re.search(RE_YEAR, line) else re.escape(line)) 109 for line in LICENSE_NOTICE)) 110 for k, v in LICENSE_PREFIX.iteritems()) 111 112if args.precommit: 113 FILE_LIST_COMMAND = 'git status -z | grep -Poz \'(?<=^[MARC][MARCD ] )[^\s]+\'' 114else: 115 FILE_LIST_COMMAND = 'git ls-tree -r --name-only -r HEAD | ' \ 116 'grep -v ^third_party/ |' \ 117 'grep -v "\(ares_config.h\|ares_build.h\)"' 118 119 120def load(name): 121 with open(name) as f: 122 return f.read() 123 124 125def save(name, text): 126 with open(name, 'w') as f: 127 f.write(text) 128 129 130assert (re.search(RE_LICENSE['Makefile'], load('Makefile'))) 131 132 133def log(cond, why, filename): 134 if not cond: return 135 if args.output == 'details': 136 print('%s: %s' % (why, filename)) 137 else: 138 print(filename) 139 140 141# scan files, validate the text 142ok = True 143filename_list = [] 144try: 145 filename_list = subprocess.check_output(FILE_LIST_COMMAND, 146 shell=True).splitlines() 147except subprocess.CalledProcessError: 148 sys.exit(0) 149 150for filename in filename_list: 151 if filename in _EXEMPT: 152 continue 153 # Skip check for upb generated code. 154 if filename.endswith('.upb.h') or filename.endswith('.upb.c'): 155 continue 156 ext = os.path.splitext(filename)[1] 157 base = os.path.basename(filename) 158 if ext in RE_LICENSE: 159 re_license = RE_LICENSE[ext] 160 elif base in RE_LICENSE: 161 re_license = RE_LICENSE[base] 162 else: 163 log(args.skips, 'skip', filename) 164 continue 165 try: 166 text = load(filename) 167 except: 168 continue 169 m = re.search(re_license, text) 170 if m: 171 pass 172 elif 'DO NOT EDIT' not in text: 173 log(1, 'copyright missing', filename) 174 ok = False 175 176sys.exit(0 if ok else 1) 177