Home | History | Annotate | Download | only in distrib
      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 
     17 import argparse
     18 import datetime
     19 import os
     20 import re
     21 import sys
     22 import subprocess
     23 
     24 # find our home
     25 ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
     26 os.chdir(ROOT)
     27 
     28 # parse command line
     29 argp = argparse.ArgumentParser(description='copyright checker')
     30 argp.add_argument(
     31     '-o', '--output', default='details', choices=['list', 'details'])
     32 argp.add_argument('-s', '--skips', default=0, action='store_const', const=1)
     33 argp.add_argument('-a', '--ancient', default=0, action='store_const', const=1)
     34 argp.add_argument('--precommit', default=False, action='store_true')
     35 args = argp.parse_args()
     36 
     37 # open the license text
     38 with 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
     45 LICENSE_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 
    109 RE_YEAR = r'Copyright (?P<first_year>[0-9]+\-)?(?P<last_year>[0-9]+) ([Tt]he )?gRPC [Aa]uthors(\.|)'
    110 RE_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 
    116 if args.precommit:
    117     FILE_LIST_COMMAND = 'git status -z | grep -Poz \'(?<=^[MARC][MARCD ] )[^\s]+\''
    118 else:
    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 
    124 def load(name):
    125     with open(name) as f:
    126         return f.read()
    127 
    128 
    129 def save(name, text):
    130     with open(name, 'w') as f:
    131         f.write(text)
    132 
    133 
    134 assert (re.search(RE_LICENSE['Makefile'], load('Makefile')))
    135 
    136 
    137 def 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
    146 ok = True
    147 filename_list = []
    148 try:
    149     filename_list = subprocess.check_output(
    150         FILE_LIST_COMMAND, shell=True).splitlines()
    151 except subprocess.CalledProcessError:
    152     sys.exit(0)
    153 
    154 for 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 
    179 sys.exit(0 if ok else 1)
    180