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 def __init__(self): 39 self.fully_qualified_re = re.compile(r"([ (<])::(grpc[A-Za-z_:])") 40 self.using_re = re.compile( 41 r"(using +|using +[A-Za-z_]+ *= *|namespace [A-Za-z_]+ *= *)::" 42 ) 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" 89) 90argp.add_argument("-f", "--fix", default=False, action="store_true") 91argp.add_argument("--precommit", default=False, action="store_true") 92args = argp.parse_args() 93 94grep_filter = r"grep -E '^(include|src|test).*\.(h|cc)$'" 95if args.precommit: 96 git_command = "git diff --name-only HEAD" 97else: 98 git_command = "git ls-tree -r --name-only -r HEAD" 99 100FILE_LIST_COMMAND = " | ".join((git_command, grep_filter)) 101 102# scan files 103ok = True 104filename_list = [] 105try: 106 filename_list = ( 107 subprocess.check_output(FILE_LIST_COMMAND, shell=True) 108 .decode() 109 .splitlines() 110 ) 111 # Filter out non-existent files (ie, file removed or renamed) 112 filename_list = (f for f in filename_list if os.path.isfile(f)) 113except subprocess.CalledProcessError: 114 sys.exit(0) 115 116validator = QualificationValidator() 117 118for filename in filename_list: 119 # Skip check for upb generated code and ignored files. 120 if ( 121 filename.endswith(".upb.h") 122 or filename.endswith(".upbdefs.h") 123 or filename.endswith(".upbdefs.c") 124 or filename in IGNORED_FILES 125 ): 126 continue 127 ok = validator.check(filename, args.fix) and ok 128 129sys.exit(0 if ok else 1) 130