aboutsummaryrefslogtreecommitdiff
path: root/tools_webrtc/presubmit_checks_lib/check_orphan_headers.py
blob: deb445cd8caaa060a2b2760074cd481fcd5efbb9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#!/usr/bin/env python
# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
#
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file in the root of the source
# tree. An additional intellectual property rights grant can be found
# in the file PATENTS.  All contributing project authors may
# be found in the AUTHORS file in the root of the source tree.

import os
import re
import string


# TARGET_RE matches a GN target, and extracts the target name and the contents.
TARGET_RE = re.compile(r'(?P<indent>\s*)\w+\("(?P<target_name>\w+)"\) {'
                       r'(?P<target_contents>.*?)'
                       r'(?P=indent)}',
                       re.MULTILINE | re.DOTALL)

# SOURCES_RE matches a block of sources inside a GN target.
SOURCES_RE = re.compile(
    r'(sources|public|common_objc_headers) \+?= \[(?P<sources>.*?)\]',
    re.MULTILINE | re.DOTALL)

SOURCE_FILE_RE = re.compile(r'.*\"(?P<source_file>.*)\"')


class NoBuildGnFoundError(Exception):
  pass


class WrongFileTypeError(Exception):
  pass


def _ReadFile(file_path):
  """Returns the content of file_path in a string.

  Args:
    file_path: the path of the file to read.
  Returns:
    A string with the content of the file.
  """
  with open(file_path) as f:
    return f.read()


def GetBuildGnPathFromFilePath(file_path, file_exists_check, root_dir_path):
  """Returns the BUILD.gn file responsible for file_path.

  Args:
    file_path: the absolute path to the .h file to check.
    file_exists_check: a function that defines how to check if a file exists
      on the file system.
    root_dir_path: the absolute path of the root of project.

  Returns:
    A string with the absolute path to the BUILD.gn file responsible to include
    file_path in a target.
  """
  if not file_path.endswith('.h'):
    raise WrongFileTypeError(
        'File {} is not an header file (.h)'.format(file_path))
  candidate_dir = os.path.dirname(file_path)
  while candidate_dir.startswith(root_dir_path):
    candidate_build_gn_path = os.path.join(candidate_dir, 'BUILD.gn')
    if file_exists_check(candidate_build_gn_path):
      return candidate_build_gn_path
    else:
      candidate_dir = os.path.abspath(os.path.join(candidate_dir,
                                                   os.pardir))
  raise NoBuildGnFoundError(
      'No BUILD.gn file found for file: `{}`'.format(file_path))


def IsHeaderInBuildGn(header_path, build_gn_path):
  """Returns True if the header is listed in the BUILD.gn file.

  Args:
    header_path: the absolute path to the header to check.
    build_gn_path: the absolute path to the header to check.

  Returns:
    bool: True if the header_path is an header that is listed in
      at least one GN target in the BUILD.gn file specified by
      the argument build_gn_path.
  """
  target_abs_path = os.path.dirname(build_gn_path)
  build_gn_content = _ReadFile(build_gn_path)
  headers_in_build_gn = GetHeadersInBuildGnFileSources(build_gn_content,
                                                       target_abs_path)
  return header_path in headers_in_build_gn


def GetHeadersInBuildGnFileSources(file_content, target_abs_path):
  """Returns a set with all the .h files in the file_content.

  Args:
    file_content: a string with the content of the BUILD.gn file.
    target_abs_path: the absolute path to the directory where the
      BUILD.gn file lives.

  Returns:
    A set with all the headers (.h file) in the file_content.
    The set contains absolute paths.
  """
  headers_in_sources = set([])
  for target_match in TARGET_RE.finditer(file_content):
    target_contents = target_match.group('target_contents')
    for sources_match in SOURCES_RE.finditer(target_contents):
      sources = sources_match.group('sources')
      for source_file_match in SOURCE_FILE_RE.finditer(sources):
        source_file = source_file_match.group('source_file')
        if source_file.endswith('.h'):
          source_file_tokens = string.split(source_file, '/')
          headers_in_sources.add(os.path.join(target_abs_path,
                                              *source_file_tokens))
  return headers_in_sources