| #!/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) |