add-iwyu.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. #!/usr/bin/env python3
  2. # Copyright 2021 gRPC authors.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import collections
  16. import os
  17. def to_inc(filename):
  18. """Given filename, synthesize what should go in an include statement to get that file"""
  19. if filename.startswith("include/"):
  20. return '<%s>' % filename[len("include/"):]
  21. return '"%s"' % filename
  22. def set_pragma(filename, pragma):
  23. """Set the file-level IWYU pragma in filename"""
  24. lines = []
  25. saw_first_define = False
  26. for line in open(filename).read().splitlines():
  27. if line.startswith('// IWYU pragma: '):
  28. continue
  29. lines.append(line)
  30. if not saw_first_define and line.startswith('#define '):
  31. saw_first_define = True
  32. lines.append('')
  33. lines.append('// IWYU pragma: %s' % pragma)
  34. lines.append('')
  35. open(filename, 'w').write('\n'.join(lines) + '\n')
  36. def set_exports(pub, cg):
  37. """In file pub, mark the include for cg with IWYU pragma: export"""
  38. lines = []
  39. for line in open(pub).read().splitlines():
  40. if line.startswith('#include %s' % to_inc(cg)):
  41. lines.append('#include %s // IWYU pragma: export' % to_inc(cg))
  42. else:
  43. lines.append(line)
  44. open(pub, 'w').write('\n'.join(lines) + '\n')
  45. CG_ROOTS_GRPC = (
  46. (r'sync', 'grpc/support/sync.h'),
  47. (r'atm', 'grpc/support/atm.h'),
  48. )
  49. CG_ROOTS_GRPCPP = []
  50. def fix_tree(tree, cg_roots):
  51. """Fix one include tree"""
  52. # Map of filename --> paths including that filename
  53. reverse_map = collections.defaultdict(list)
  54. # The same, but for things with '/impl/codegen' in their names
  55. cg_reverse_map = collections.defaultdict(list)
  56. for root, dirs, files in os.walk(tree):
  57. root_map = cg_reverse_map if '/impl/codegen' in root else reverse_map
  58. for filename in files:
  59. root_map[filename].append(root)
  60. # For each thing in '/impl/codegen' figure out what exports it
  61. for filename, paths in cg_reverse_map.items():
  62. # Exclude non-headers
  63. if not filename.endswith('.h'):
  64. continue
  65. pragma = None
  66. # Check for our 'special' headers: if we see one of these, we just
  67. # hardcode where they go to because there's some complicated rules.
  68. for root, target in cg_roots:
  69. if filename.startswith(root):
  70. pragma = 'private, include <%s>' % target
  71. if len(paths) == 1:
  72. path = paths[0]
  73. if filename.startswith(root + '.'):
  74. set_exports('include/' + target, path + '/' + filename)
  75. if filename.startswith(root + '_'):
  76. set_exports(path + '/' + root + '.h',
  77. path + '/' + filename)
  78. # If the path for a file in /impl/codegen is ambiguous, just don't bother
  79. if not pragma and len(paths) == 1:
  80. path = paths[0]
  81. # Check if we have an exporting candidate
  82. if filename in reverse_map:
  83. proper = reverse_map[filename]
  84. # And that it too is unambiguous
  85. if len(proper) == 1:
  86. # Build the two relevant pathnames
  87. cg = path + '/' + filename
  88. pub = proper[0] + '/' + filename
  89. # And see if the public file actually includes the /impl/codegen file
  90. if ('#include %s' % to_inc(cg)) in open(pub).read():
  91. # Finally, if it does, we'll set that pragma
  92. pragma = 'private, include %s' % to_inc(pub)
  93. # And mark the export
  94. set_exports(pub, cg)
  95. # If we can't find a good alternative include to point people to,
  96. # mark things private anyway... we don't want to recommend people include
  97. # from impl/codegen
  98. if not pragma:
  99. pragma = 'private'
  100. for path in paths:
  101. set_pragma(path + '/' + filename, pragma)
  102. fix_tree('include/grpc', CG_ROOTS_GRPC)
  103. fix_tree('include/grpcpp', CG_ROOTS_GRPCPP)