1#!/usr/bin/env python3 2 3# Copyright 2022 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 os 19import os.path 20import re 21import subprocess 22import sys 23 24# TODO(hork): dedupe args/load/validate/save code with other check scripts. 25 26 27def load(fpath): 28 with open(fpath, 'r') as f: 29 return f.readlines() 30 31 32def save(fpath, contents): 33 with open(fpath, 'w') as f: 34 f.write(contents) 35 36 37class QualificationValidator(object): 38 39 def __init__(self): 40 self.fully_qualified_re = re.compile(r'([ (<])::(grpc[A-Za-z_:])') 41 self.using_re = re.compile( 42 r'(using +|using +[A-Za-z_]+ *= *|namespace [A-Za-z_]+ *= *)::') 43 self.define_re = re.compile(r'^#define') 44 45 def check(self, fpath, fix): 46 fcontents = load(fpath) 47 failed = False 48 for (i, line) in enumerate(fcontents): 49 if not self.fully_qualified_re.search(line): 50 continue 51 # skip `using` statements 52 if self.using_re.search(line): 53 continue 54 # skip `#define` statements 55 if self.define_re.search(line): 56 continue 57 # fully-qualified namespace found, which may be unnecessary 58 if fix: 59 fcontents[i] = self.fully_qualified_re.sub(r'\1\2', line) 60 else: 61 print("Found in %s:%d - %s" % (fpath, i, line.strip())) 62 failed = True 63 if fix: 64 save(fpath, ''.join(fcontents)) 65 return not failed 66 67 68IGNORED_FILES = [ 69 # TODO(hork): rename symbols to avoid the need for fully-qualified names 70 "src/cpp/common/core_codegen.cc", 71 # TODO(hork): This could be a breaking change for users that define their 72 # own (possibly nested) `grpc.*` namespaces that contain conflicting 73 # symbols. It may be worth trying to land this change at some point, as 74 # users would be better off using unique namespaces. 75 "src/compiler/cpp_generator.cc", 76 # multi-line #define statements are not handled 77 "src/core/lib/gprpp/global_config_env.h", 78 "src/core/lib/profiling/timers.h", 79 "src/core/lib/gprpp/crash.h" 80] 81 82# find our home 83ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..')) 84os.chdir(ROOT) 85 86# parse command line 87argp = argparse.ArgumentParser( 88 description='c++ namespace full qualification checker') 89argp.add_argument('-f', '--fix', default=False, action='store_true') 90argp.add_argument('--precommit', default=False, action='store_true') 91args = argp.parse_args() 92 93grep_filter = r"grep -E '^(include|src|test).*\.(h|cc)$'" 94if args.precommit: 95 git_command = 'git diff --name-only HEAD' 96else: 97 git_command = 'git ls-tree -r --name-only -r HEAD' 98 99FILE_LIST_COMMAND = ' | '.join((git_command, grep_filter)) 100 101# scan files 102ok = True 103filename_list = [] 104try: 105 filename_list = subprocess.check_output(FILE_LIST_COMMAND, 106 shell=True).decode().splitlines() 107 # Filter out non-existent files (ie, file removed or renamed) 108 filename_list = (f for f in filename_list if os.path.isfile(f)) 109except subprocess.CalledProcessError: 110 sys.exit(0) 111 112validator = QualificationValidator() 113 114for filename in filename_list: 115 # Skip check for upb generated code and ignored files. 116 if (filename.endswith('.upb.h') or filename.endswith('.upb.c') or 117 filename.endswith('.upbdefs.h') or 118 filename.endswith('.upbdefs.c') or filename in IGNORED_FILES): 119 continue 120 ok = validator.check(filename, args.fix) and ok 121 122sys.exit(0 if ok else 1) 123