xref: /aosp_15_r20/external/webrtc/tools_webrtc/gn_check_autofix.py (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1*d9f75844SAndroid Build Coastguard Worker#!/usr/bin/env vpython3
2*d9f75844SAndroid Build Coastguard Worker
3*d9f75844SAndroid Build Coastguard Worker# Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
4*d9f75844SAndroid Build Coastguard Worker#
5*d9f75844SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license
6*d9f75844SAndroid Build Coastguard Worker# that can be found in the LICENSE file in the root of the source
7*d9f75844SAndroid Build Coastguard Worker# tree. An additional intellectual property rights grant can be found
8*d9f75844SAndroid Build Coastguard Worker# in the file PATENTS.  All contributing project authors may
9*d9f75844SAndroid Build Coastguard Worker# be found in the AUTHORS file in the root of the source tree.
10*d9f75844SAndroid Build Coastguard Worker"""
11*d9f75844SAndroid Build Coastguard WorkerThis tool tries to fix (some) errors reported by `gn gen --check` or
12*d9f75844SAndroid Build Coastguard Worker`gn check`.
13*d9f75844SAndroid Build Coastguard WorkerIt will run `mb gen` in a temporary directory and it is really useful to
14*d9f75844SAndroid Build Coastguard Workercheck for different configurations.
15*d9f75844SAndroid Build Coastguard Worker
16*d9f75844SAndroid Build Coastguard WorkerUsage:
17*d9f75844SAndroid Build Coastguard Worker    $ vpython3 tools_webrtc/gn_check_autofix.py -m some_mater -b some_bot
18*d9f75844SAndroid Build Coastguard Worker    or
19*d9f75844SAndroid Build Coastguard Worker    $ vpython3 tools_webrtc/gn_check_autofix.py -c some_mb_config
20*d9f75844SAndroid Build Coastguard Worker"""
21*d9f75844SAndroid Build Coastguard Worker
22*d9f75844SAndroid Build Coastguard Workerimport os
23*d9f75844SAndroid Build Coastguard Workerimport re
24*d9f75844SAndroid Build Coastguard Workerimport shutil
25*d9f75844SAndroid Build Coastguard Workerimport subprocess
26*d9f75844SAndroid Build Coastguard Workerimport sys
27*d9f75844SAndroid Build Coastguard Workerimport tempfile
28*d9f75844SAndroid Build Coastguard Worker
29*d9f75844SAndroid Build Coastguard Workerfrom collections import defaultdict
30*d9f75844SAndroid Build Coastguard Worker
31*d9f75844SAndroid Build Coastguard WorkerSCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
32*d9f75844SAndroid Build Coastguard Worker
33*d9f75844SAndroid Build Coastguard WorkerCHROMIUM_DIRS = [
34*d9f75844SAndroid Build Coastguard Worker    'base', 'build', 'buildtools', 'testing', 'third_party', 'tools'
35*d9f75844SAndroid Build Coastguard Worker]
36*d9f75844SAndroid Build Coastguard Worker
37*d9f75844SAndroid Build Coastguard WorkerTARGET_RE = re.compile(
38*d9f75844SAndroid Build Coastguard Worker    r'(?P<indentation_level>\s*)\w*\("(?P<target_name>\w*)"\) {$')
39*d9f75844SAndroid Build Coastguard Worker
40*d9f75844SAndroid Build Coastguard Worker
41*d9f75844SAndroid Build Coastguard Workerclass TemporaryDirectory:
42*d9f75844SAndroid Build Coastguard Worker  def __init__(self):
43*d9f75844SAndroid Build Coastguard Worker    self._closed = False
44*d9f75844SAndroid Build Coastguard Worker    self._name = None
45*d9f75844SAndroid Build Coastguard Worker    self._name = tempfile.mkdtemp()
46*d9f75844SAndroid Build Coastguard Worker
47*d9f75844SAndroid Build Coastguard Worker  def __enter__(self):
48*d9f75844SAndroid Build Coastguard Worker    return self._name
49*d9f75844SAndroid Build Coastguard Worker
50*d9f75844SAndroid Build Coastguard Worker  def __exit__(self, exc, value, _tb):
51*d9f75844SAndroid Build Coastguard Worker    if self._name and not self._closed:
52*d9f75844SAndroid Build Coastguard Worker      shutil.rmtree(self._name)
53*d9f75844SAndroid Build Coastguard Worker      self._closed = True
54*d9f75844SAndroid Build Coastguard Worker
55*d9f75844SAndroid Build Coastguard Worker
56*d9f75844SAndroid Build Coastguard Workerdef Run(cmd):
57*d9f75844SAndroid Build Coastguard Worker  print('Running:', ' '.join(cmd))
58*d9f75844SAndroid Build Coastguard Worker  sub = subprocess.Popen(cmd,
59*d9f75844SAndroid Build Coastguard Worker                         stdout=subprocess.PIPE,
60*d9f75844SAndroid Build Coastguard Worker                         stderr=subprocess.PIPE,
61*d9f75844SAndroid Build Coastguard Worker                         universal_newlines=True)
62*d9f75844SAndroid Build Coastguard Worker  return sub.communicate()
63*d9f75844SAndroid Build Coastguard Worker
64*d9f75844SAndroid Build Coastguard Worker
65*d9f75844SAndroid Build Coastguard Workerdef FixErrors(filename, missing_deps, deleted_sources):
66*d9f75844SAndroid Build Coastguard Worker  with open(filename) as f:
67*d9f75844SAndroid Build Coastguard Worker    lines = f.readlines()
68*d9f75844SAndroid Build Coastguard Worker
69*d9f75844SAndroid Build Coastguard Worker  fixed_file = ''
70*d9f75844SAndroid Build Coastguard Worker  indentation_level = None
71*d9f75844SAndroid Build Coastguard Worker  for line in lines:
72*d9f75844SAndroid Build Coastguard Worker    match = TARGET_RE.match(line)
73*d9f75844SAndroid Build Coastguard Worker    if match:
74*d9f75844SAndroid Build Coastguard Worker      target = match.group('target_name')
75*d9f75844SAndroid Build Coastguard Worker      if target in missing_deps:
76*d9f75844SAndroid Build Coastguard Worker        indentation_level = match.group('indentation_level')
77*d9f75844SAndroid Build Coastguard Worker    elif indentation_level is not None:
78*d9f75844SAndroid Build Coastguard Worker      match = re.match(indentation_level + '}$', line)
79*d9f75844SAndroid Build Coastguard Worker      if match:
80*d9f75844SAndroid Build Coastguard Worker        line = ('deps = [\n' + ''.join('  "' + dep + '",\n'
81*d9f75844SAndroid Build Coastguard Worker                                       for dep in missing_deps[target]) +
82*d9f75844SAndroid Build Coastguard Worker                ']\n') + line
83*d9f75844SAndroid Build Coastguard Worker        indentation_level = None
84*d9f75844SAndroid Build Coastguard Worker      elif line.strip().startswith('deps = ['):
85*d9f75844SAndroid Build Coastguard Worker        joined_deps = ''.join('  "' + dep + '",\n'
86*d9f75844SAndroid Build Coastguard Worker                              for dep in missing_deps[target])
87*d9f75844SAndroid Build Coastguard Worker        line = line.replace('deps = [', 'deps = [' + joined_deps)
88*d9f75844SAndroid Build Coastguard Worker        indentation_level = None
89*d9f75844SAndroid Build Coastguard Worker
90*d9f75844SAndroid Build Coastguard Worker    if line.strip() not in deleted_sources:
91*d9f75844SAndroid Build Coastguard Worker      fixed_file += line
92*d9f75844SAndroid Build Coastguard Worker
93*d9f75844SAndroid Build Coastguard Worker  with open(filename, 'w') as f:
94*d9f75844SAndroid Build Coastguard Worker    f.write(fixed_file)
95*d9f75844SAndroid Build Coastguard Worker
96*d9f75844SAndroid Build Coastguard Worker  Run(['gn', 'format', filename])
97*d9f75844SAndroid Build Coastguard Worker
98*d9f75844SAndroid Build Coastguard Worker
99*d9f75844SAndroid Build Coastguard Workerdef FirstNonEmpty(iterable):
100*d9f75844SAndroid Build Coastguard Worker  """Return first item which evaluates to True, or fallback to None."""
101*d9f75844SAndroid Build Coastguard Worker  return next((x for x in iterable if x), None)
102*d9f75844SAndroid Build Coastguard Worker
103*d9f75844SAndroid Build Coastguard Worker
104*d9f75844SAndroid Build Coastguard Workerdef Rebase(base_path, dependency_path, dependency):
105*d9f75844SAndroid Build Coastguard Worker  """Adapt paths so they work both in stand-alone WebRTC and Chromium tree.
106*d9f75844SAndroid Build Coastguard Worker
107*d9f75844SAndroid Build Coastguard Worker  To cope with varying top-level directory (WebRTC VS Chromium), we use:
108*d9f75844SAndroid Build Coastguard Worker    * relative paths for WebRTC modules.
109*d9f75844SAndroid Build Coastguard Worker    * absolute paths for shared ones.
110*d9f75844SAndroid Build Coastguard Worker  E.g. '//common_audio/...' -> '../../common_audio/'
111*d9f75844SAndroid Build Coastguard Worker       '//third_party/...' remains as is.
112*d9f75844SAndroid Build Coastguard Worker
113*d9f75844SAndroid Build Coastguard Worker  Args:
114*d9f75844SAndroid Build Coastguard Worker    base_path: current module path  (E.g. '//video')
115*d9f75844SAndroid Build Coastguard Worker    dependency_path: path from root (E.g. '//rtc_base/time')
116*d9f75844SAndroid Build Coastguard Worker    dependency: target itself       (E.g. 'timestamp_extrapolator')
117*d9f75844SAndroid Build Coastguard Worker
118*d9f75844SAndroid Build Coastguard Worker  Returns:
119*d9f75844SAndroid Build Coastguard Worker    Full target path (E.g. '../rtc_base/time:timestamp_extrapolator').
120*d9f75844SAndroid Build Coastguard Worker  """
121*d9f75844SAndroid Build Coastguard Worker
122*d9f75844SAndroid Build Coastguard Worker  root = FirstNonEmpty(dependency_path.split('/'))
123*d9f75844SAndroid Build Coastguard Worker  if root in CHROMIUM_DIRS:
124*d9f75844SAndroid Build Coastguard Worker    # Chromium paths must remain absolute. E.g. //third_party//abseil-cpp...
125*d9f75844SAndroid Build Coastguard Worker    rebased = dependency_path
126*d9f75844SAndroid Build Coastguard Worker  else:
127*d9f75844SAndroid Build Coastguard Worker    base_path = base_path.split(os.path.sep)
128*d9f75844SAndroid Build Coastguard Worker    dependency_path = dependency_path.split(os.path.sep)
129*d9f75844SAndroid Build Coastguard Worker
130*d9f75844SAndroid Build Coastguard Worker    first_difference = None
131*d9f75844SAndroid Build Coastguard Worker    shortest_length = min(len(dependency_path), len(base_path))
132*d9f75844SAndroid Build Coastguard Worker    for i in range(shortest_length):
133*d9f75844SAndroid Build Coastguard Worker      if dependency_path[i] != base_path[i]:
134*d9f75844SAndroid Build Coastguard Worker        first_difference = i
135*d9f75844SAndroid Build Coastguard Worker        break
136*d9f75844SAndroid Build Coastguard Worker
137*d9f75844SAndroid Build Coastguard Worker    first_difference = first_difference or shortest_length
138*d9f75844SAndroid Build Coastguard Worker    base_path = base_path[first_difference:]
139*d9f75844SAndroid Build Coastguard Worker    dependency_path = dependency_path[first_difference:]
140*d9f75844SAndroid Build Coastguard Worker    rebased = os.path.sep.join((['..'] * len(base_path)) + dependency_path)
141*d9f75844SAndroid Build Coastguard Worker  return rebased + ':' + dependency
142*d9f75844SAndroid Build Coastguard Worker
143*d9f75844SAndroid Build Coastguard Worker
144*d9f75844SAndroid Build Coastguard Workerdef main():
145*d9f75844SAndroid Build Coastguard Worker  deleted_sources = set()
146*d9f75844SAndroid Build Coastguard Worker  errors_by_file = defaultdict(lambda: defaultdict(set))
147*d9f75844SAndroid Build Coastguard Worker
148*d9f75844SAndroid Build Coastguard Worker  with TemporaryDirectory() as tmp_dir:
149*d9f75844SAndroid Build Coastguard Worker    mb_script_path = os.path.join(SCRIPT_DIR, 'mb', 'mb.py')
150*d9f75844SAndroid Build Coastguard Worker    mb_config_file_path = os.path.join(SCRIPT_DIR, 'mb', 'mb_config.pyl')
151*d9f75844SAndroid Build Coastguard Worker    mb_gen_command = ([
152*d9f75844SAndroid Build Coastguard Worker        mb_script_path,
153*d9f75844SAndroid Build Coastguard Worker        'gen',
154*d9f75844SAndroid Build Coastguard Worker        tmp_dir,
155*d9f75844SAndroid Build Coastguard Worker        '--config-file',
156*d9f75844SAndroid Build Coastguard Worker        mb_config_file_path,
157*d9f75844SAndroid Build Coastguard Worker    ] + sys.argv[1:])
158*d9f75844SAndroid Build Coastguard Worker
159*d9f75844SAndroid Build Coastguard Worker  mb_output = Run(mb_gen_command)
160*d9f75844SAndroid Build Coastguard Worker  errors = mb_output[0].split('ERROR')[1:]
161*d9f75844SAndroid Build Coastguard Worker
162*d9f75844SAndroid Build Coastguard Worker  if mb_output[1]:
163*d9f75844SAndroid Build Coastguard Worker    print(mb_output[1])
164*d9f75844SAndroid Build Coastguard Worker    return 1
165*d9f75844SAndroid Build Coastguard Worker
166*d9f75844SAndroid Build Coastguard Worker  for error in errors:
167*d9f75844SAndroid Build Coastguard Worker    error = error.split('\n')
168*d9f75844SAndroid Build Coastguard Worker    target_msg = 'The target:'
169*d9f75844SAndroid Build Coastguard Worker    if target_msg not in error:
170*d9f75844SAndroid Build Coastguard Worker      target_msg = 'It is not in any dependency of'
171*d9f75844SAndroid Build Coastguard Worker    if target_msg not in error:
172*d9f75844SAndroid Build Coastguard Worker      print('\n'.join(error))
173*d9f75844SAndroid Build Coastguard Worker      continue
174*d9f75844SAndroid Build Coastguard Worker    index = error.index(target_msg) + 1
175*d9f75844SAndroid Build Coastguard Worker    path, target = error[index].strip().split(':')
176*d9f75844SAndroid Build Coastguard Worker    if error[index + 1] in ('is including a file from the target:',
177*d9f75844SAndroid Build Coastguard Worker                            'The include file is in the target(s):'):
178*d9f75844SAndroid Build Coastguard Worker      dep = error[index + 2].strip()
179*d9f75844SAndroid Build Coastguard Worker      dep_path, dep = dep.split(':')
180*d9f75844SAndroid Build Coastguard Worker      dep = Rebase(path, dep_path, dep)
181*d9f75844SAndroid Build Coastguard Worker      # Replacing /target:target with /target
182*d9f75844SAndroid Build Coastguard Worker      dep = re.sub(r'/(\w+):(\1)$', r'/\1', dep)
183*d9f75844SAndroid Build Coastguard Worker      # Replacing target:target with target
184*d9f75844SAndroid Build Coastguard Worker      dep = re.sub(r'^(\w+):(\1)$', r'\1', dep)
185*d9f75844SAndroid Build Coastguard Worker      path = os.path.join(path[2:], 'BUILD.gn')
186*d9f75844SAndroid Build Coastguard Worker      errors_by_file[path][target].add(dep)
187*d9f75844SAndroid Build Coastguard Worker    elif error[index + 1] == 'has a source file:':
188*d9f75844SAndroid Build Coastguard Worker      deleted_file = '"' + os.path.basename(error[index + 2].strip()) + '",'
189*d9f75844SAndroid Build Coastguard Worker      deleted_sources.add(deleted_file)
190*d9f75844SAndroid Build Coastguard Worker    else:
191*d9f75844SAndroid Build Coastguard Worker      print('\n'.join(error))
192*d9f75844SAndroid Build Coastguard Worker      continue
193*d9f75844SAndroid Build Coastguard Worker
194*d9f75844SAndroid Build Coastguard Worker  for path, missing_deps in list(errors_by_file.items()):
195*d9f75844SAndroid Build Coastguard Worker    FixErrors(path, missing_deps, deleted_sources)
196*d9f75844SAndroid Build Coastguard Worker
197*d9f75844SAndroid Build Coastguard Worker  return 0
198*d9f75844SAndroid Build Coastguard Worker
199*d9f75844SAndroid Build Coastguard Worker
200*d9f75844SAndroid Build Coastguard Workerif __name__ == '__main__':
201*d9f75844SAndroid Build Coastguard Worker  sys.exit(main())
202