#!/usr/bin/env python3 # Copyright 2021 gRPC authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import collections import os def to_inc(filename): """Given filename, synthesize what should go in an include statement to get that file""" if filename.startswith("include/"): return '<%s>' % filename[len("include/"):] return '"%s"' % filename def set_pragmas(filename, pragmas): """Set the file-level IWYU pragma in filename""" lines = [] saw_first_define = False for line in open(filename).read().splitlines(): if line.startswith('// IWYU pragma: '): continue lines.append(line) if not saw_first_define and line.startswith('#define '): saw_first_define = True lines.append('') for pragma in pragmas: lines.append('// IWYU pragma: %s' % pragma) lines.append('') open(filename, 'w').write('\n'.join(lines) + '\n') def set_exports(pub, cg): """In file pub, mark the include for cg with IWYU pragma: export""" lines = [] for line in open(pub).read().splitlines(): if line.startswith('#include %s' % to_inc(cg)): lines.append('#include %s // IWYU pragma: export' % to_inc(cg)) else: lines.append(line) open(pub, 'w').write('\n'.join(lines) + '\n') CG_ROOTS_GRPC = ( (r'sync', 'grpc/support/sync.h', False), (r'atm', 'grpc/support/atm.h', False), (r'grpc_types', 'grpc/grpc.h', True), (r'gpr_types', 'grpc/grpc.h', True), (r'compression_types', 'grpc/compression.h', True), (r'connectivity_state', 'grpc/grpc.h', True), ) CG_ROOTS_GRPCPP = [ (r'status_code_enum', 'grpcpp/support/status.h', False), ] def fix_tree(tree, cg_roots): """Fix one include tree""" # Map of filename --> paths including that filename reverse_map = collections.defaultdict(list) # The same, but for things with '/impl/codegen' in their names cg_reverse_map = collections.defaultdict(list) for root, dirs, files in os.walk(tree): root_map = cg_reverse_map if '/impl/codegen' in root else reverse_map for filename in files: root_map[filename].append(root) # For each thing in '/impl/codegen' figure out what exports it for filename, paths in cg_reverse_map.items(): print("****", filename) # Exclude non-headers if not filename.endswith('.h'): continue pragmas = [] # Check for our 'special' headers: if we see one of these, we just # hardcode where they go to because there's some complicated rules. for root, target, friend in cg_roots: print(root, target, friend) if filename.startswith(root): pragmas = ['private, include <%s>' % target] if friend: pragmas.append('friend "src/.*"') if len(paths) == 1: path = paths[0] if filename.startswith(root + '.'): set_exports('include/' + target, path + '/' + filename) if filename.startswith(root + '_'): set_exports(path + '/' + root + '.h', path + '/' + filename) # If the path for a file in /impl/codegen is ambiguous, just don't bother if not pragmas and len(paths) == 1: path = paths[0] # Check if we have an exporting candidate if filename in reverse_map: proper = reverse_map[filename] # And that it too is unambiguous if len(proper) == 1: # Build the two relevant pathnames cg = path + '/' + filename pub = proper[0] + '/' + filename # And see if the public file actually includes the /impl/codegen file if ('#include %s' % to_inc(cg)) in open(pub).read(): # Finally, if it does, we'll set that pragma pragmas = ['private, include %s' % to_inc(pub)] # And mark the export set_exports(pub, cg) # If we can't find a good alternative include to point people to, # mark things private anyway... we don't want to recommend people include # from impl/codegen if not pragmas: pragmas = ['private'] for path in paths: set_pragmas(path + '/' + filename, pragmas) fix_tree('include/grpc', CG_ROOTS_GRPC) fix_tree('include/grpcpp', CG_ROOTS_GRPCPP)