xref: /aosp_15_r20/external/webrtc/tools_webrtc/presubmit_checks_lib/check_orphan_headers.py (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1#!/usr/bin/env vpython3
2
3# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
4#
5# Use of this source code is governed by a BSD-style license
6# that can be found in the LICENSE file in the root of the source
7# tree. An additional intellectual property rights grant can be found
8# in the file PATENTS.  All contributing project authors may
9# be found in the AUTHORS file in the root of the source tree.
10
11import os
12import re
13
14# TARGET_RE matches a GN target, and extracts the target name and the contents.
15TARGET_RE = re.compile(
16    r'(?P<indent>\s*)\w+\("(?P<target_name>\w+)"\) {'
17    r'(?P<target_contents>.*?)'
18    r'(?P=indent)}', re.MULTILINE | re.DOTALL)
19
20# SOURCES_RE matches a block of sources inside a GN target.
21SOURCES_RE = re.compile(
22    r'(sources|public|common_objc_headers) \+?= \[(?P<sources>.*?)\]',
23    re.MULTILINE | re.DOTALL)
24
25SOURCE_FILE_RE = re.compile(r'.*\"(?P<source_file>.*)\"')
26
27
28class NoBuildGnFoundError(Exception):
29  pass
30
31
32class WrongFileTypeError(Exception):
33  pass
34
35
36def _ReadFile(file_path):
37  """Returns the content of file_path in a string.
38
39  Args:
40    file_path: the path of the file to read.
41  Returns:
42    A string with the content of the file.
43  """
44  with open(file_path) as f:
45    return f.read()
46
47
48def GetBuildGnPathFromFilePath(file_path, file_exists_check, root_dir_path):
49  """Returns the BUILD.gn file responsible for file_path.
50
51  Args:
52    file_path: the absolute path to the .h file to check.
53    file_exists_check: a function that defines how to check if a file exists
54      on the file system.
55    root_dir_path: the absolute path of the root of project.
56
57  Returns:
58    A string with the absolute path to the BUILD.gn file responsible to include
59    file_path in a target.
60  """
61  if not file_path.endswith('.h'):
62    raise WrongFileTypeError(
63        'File {} is not an header file (.h)'.format(file_path))
64  candidate_dir = os.path.dirname(file_path)
65  while candidate_dir.startswith(root_dir_path):
66    candidate_build_gn_path = os.path.join(candidate_dir, 'BUILD.gn')
67    if file_exists_check(candidate_build_gn_path):
68      return candidate_build_gn_path
69    candidate_dir = os.path.abspath(os.path.join(candidate_dir, os.pardir))
70  raise NoBuildGnFoundError(
71      'No BUILD.gn file found for file: `{}`'.format(file_path))
72
73
74def IsHeaderInBuildGn(header_path, build_gn_path):
75  """Returns True if the header is listed in the BUILD.gn file.
76
77  Args:
78    header_path: the absolute path to the header to check.
79    build_gn_path: the absolute path to the header to check.
80
81  Returns:
82    bool: True if the header_path is an header that is listed in
83      at least one GN target in the BUILD.gn file specified by
84      the argument build_gn_path.
85  """
86  target_abs_path = os.path.dirname(build_gn_path)
87  build_gn_content = _ReadFile(build_gn_path)
88  headers_in_build_gn = GetHeadersInBuildGnFileSources(build_gn_content,
89                                                       target_abs_path)
90  return header_path in headers_in_build_gn
91
92
93def GetHeadersInBuildGnFileSources(file_content, target_abs_path):
94  """Returns a set with all the .h files in the file_content.
95
96  Args:
97    file_content: a string with the content of the BUILD.gn file.
98    target_abs_path: the absolute path to the directory where the
99      BUILD.gn file lives.
100
101  Returns:
102    A set with all the headers (.h file) in the file_content.
103    The set contains absolute paths.
104  """
105  headers_in_sources = set([])
106  for target_match in TARGET_RE.finditer(file_content):
107    target_contents = target_match.group('target_contents')
108    for sources_match in SOURCES_RE.finditer(target_contents):
109      sources = sources_match.group('sources')
110      for source_file_match in SOURCE_FILE_RE.finditer(sources):
111        source_file = source_file_match.group('source_file')
112        if source_file.endswith('.h'):
113          source_file_tokens = source_file.split('/')
114          headers_in_sources.add(
115              os.path.join(target_abs_path, *source_file_tokens))
116  return headers_in_sources
117