remove_duplicate_includes.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. #!/usr/bin/env python
  2. # Copyright 2016 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. """This script will search through the target folder specified and try to find
  6. duplicate includes from h and cc files, and remove them from the cc files. The
  7. current/working directory needs to be chromium_checkout/src/ when this tool is
  8. run.
  9. Usage: remove_duplicate_includes.py --dry-run components/foo components/bar
  10. """
  11. from __future__ import print_function
  12. import argparse
  13. import collections
  14. import logging
  15. import os
  16. import re
  17. import sys
  18. # This could be generalized if desired, and moved to command line arguments.
  19. H_FILE_SUFFIX = '.h'
  20. CC_FILE_SUFFIX = '.cc'
  21. # The \s should allow us to ignore any whitespace and only focus on the group
  22. # captured when comparing between files.
  23. INCLUDE_REGEX = re.compile('^\s*(#include\s+[\"<](.*?)[\">])\s*$')
  24. def HasSuffix(file_name, suffix):
  25. return os.path.splitext(file_name)[1] == suffix
  26. def IsEmpty(line):
  27. return not line.strip()
  28. def FindIncludeSet(input_lines, h_path_to_include_set, cc_file_name):
  29. """Finds and returns the corresponding include set for the given .cc file.
  30. This is done by finding the first include in the file and then trying to look
  31. up an .h file in the passed in map. If not present, then None is returned
  32. immediately.
  33. """
  34. for line in input_lines:
  35. match = INCLUDE_REGEX.search(line)
  36. # The first include match should be the corresponding .h file, else skip.
  37. if match:
  38. h_file_path = os.path.join(os.getcwd(), match.group(2))
  39. if h_file_path not in h_path_to_include_set:
  40. print('First include did not match to a known .h file, skipping ' + \
  41. cc_file_name + ', line: ' + match.group(1))
  42. return None
  43. return h_path_to_include_set[h_file_path]
  44. def WithoutDuplicates(input_lines, include_set, cc_file_name):
  45. """Checks every input line and sees if we can remove it based on the contents
  46. of the given include set.
  47. Returns what the new contents of the file should be.
  48. """
  49. output_lines = []
  50. # When a section of includes are completely removed, we want to remove the
  51. # trailing empty as well.
  52. lastCopiedLineWasEmpty = False
  53. lastLineWasOmitted = False
  54. for line in input_lines:
  55. match = INCLUDE_REGEX.search(line)
  56. if match and match.group(2) in include_set:
  57. print('Removed ' + match.group(1) + ' from ' + cc_file_name)
  58. lastLineWasOmitted = True
  59. elif lastCopiedLineWasEmpty and lastLineWasOmitted and IsEmpty(line):
  60. print('Removed empty line from ' + cc_file_name)
  61. lastLineWasOmitted = True
  62. else:
  63. lastCopiedLineWasEmpty = IsEmpty(line)
  64. lastLineWasOmitted = False
  65. output_lines.append(line)
  66. return output_lines
  67. def main():
  68. parser = argparse.ArgumentParser()
  69. parser.add_argument('--dry-run', action='store_true',
  70. help='Does not actually remove lines when specified.')
  71. parser.add_argument('targets', nargs='+',
  72. help='Relative path to folders to search for duplicate includes in.')
  73. args = parser.parse_args()
  74. # A map of header file paths to the includes they contain.
  75. h_path_to_include_set = {}
  76. # Simply collects the path of all cc files present.
  77. cc_file_path_set = set()
  78. for relative_root in args.targets:
  79. absolute_root = os.path.join(os.getcwd(), relative_root)
  80. for dir_path, dir_name_list, file_name_list in os.walk(absolute_root):
  81. for file_name in file_name_list:
  82. file_path = os.path.join(dir_path, file_name)
  83. if HasSuffix(file_name, H_FILE_SUFFIX):
  84. # By manually adding the set instead of using defaultdict we can avoid
  85. # warning about missing .h files when the .h file has no includes.
  86. h_path_to_include_set[file_path] = set()
  87. with open(file_path) as fh:
  88. for line in fh:
  89. match = INCLUDE_REGEX.search(line)
  90. if match:
  91. h_path_to_include_set[file_path].add(match.group(2))
  92. elif HasSuffix(file_name, CC_FILE_SUFFIX):
  93. cc_file_path_set.add(file_path)
  94. for cc_file_path in cc_file_path_set:
  95. cc_file_name = os.path.basename(cc_file_path)
  96. with open(cc_file_path, 'r' if args.dry_run else 'r+') as fh:
  97. # Read out all lines and reset file position to allow overwriting.
  98. input_lines = fh.readlines()
  99. fh.seek(0)
  100. include_set = FindIncludeSet(input_lines, h_path_to_include_set,
  101. cc_file_name)
  102. if include_set:
  103. output_lines = WithoutDuplicates(input_lines, include_set, cc_file_name)
  104. if not args.dry_run:
  105. fh.writelines(output_lines)
  106. fh.truncate()
  107. if __name__ == '__main__':
  108. sys.exit(main())