blob: b939636d0c831e928772389ab5f19155eddefa6f [file] [log] [blame]
ehmaldonado01653b12016-12-08 07:27:37 -08001#!/usr/bin/env python
2
3# Copyright (c) 2016 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
mbonadei235d5cc2016-12-20 07:19:18 -080011"""
12This tool tries to fix (some) errors reported by `gn gen --check` or
13`gn check`.
14It will run `mb gen` in a temporary directory and it is really useful to
15check for different configurations.
16
17Usage:
Henrik Kjellander90fd7d82017-05-09 08:30:10 +020018 $ python tools_webrtc/gn_check_autofix.py -m some_mater -b some_bot
mbonadei235d5cc2016-12-20 07:19:18 -080019 or
Henrik Kjellander90fd7d82017-05-09 08:30:10 +020020 $ python tools_webrtc/gn_check_autofix.py -c some_mb_config
mbonadei235d5cc2016-12-20 07:19:18 -080021"""
22
ehmaldonado01653b12016-12-08 07:27:37 -080023import os
24import re
25import shutil
26import subprocess
27import sys
28import tempfile
29
30from collections import defaultdict
31
mbonadei235d5cc2016-12-20 07:19:18 -080032SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
33
ehmaldonado01653b12016-12-08 07:27:37 -080034TARGET_RE = re.compile(
35 r'(?P<indentation_level>\s*)\w*\("(?P<target_name>\w*)"\) {$')
36
37class TemporaryDirectory(object):
38 def __init__(self):
39 self._closed = False
40 self._name = None
41 self._name = tempfile.mkdtemp()
42
43 def __enter__(self):
44 return self._name
45
kjellanderc88b5d52017-04-05 06:42:43 -070046 def __exit__(self, exc, value, _tb):
ehmaldonado01653b12016-12-08 07:27:37 -080047 if self._name and not self._closed:
48 shutil.rmtree(self._name)
49 self._closed = True
50
51
52def Run(cmd):
53 print 'Running:', ' '.join(cmd)
54 sub = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
55 return sub.communicate()
56
57def FixErrors(filename, missing_deps, deleted_sources):
58 with open(filename) as f:
59 lines = f.readlines()
60
61 fixed_file = ''
62 indentation_level = None
63 for line in lines:
64 match = TARGET_RE.match(line)
65 if match:
66 target = match.group('target_name')
67 if target in missing_deps:
68 indentation_level = match.group('indentation_level')
69 elif indentation_level is not None:
70 match = re.match(indentation_level + '}$', line)
71 if match:
72 line = ('deps = [\n' +
73 ''.join(' "' + dep + '",\n' for dep in missing_deps[target]) +
74 ']\n') + line
75 indentation_level = None
76 elif line.strip().startswith('deps'):
77 is_empty_deps = line.strip() == 'deps = []'
78 line = 'deps = [\n' if is_empty_deps else line
79 line += ''.join(' "' + dep + '",\n' for dep in missing_deps[target])
80 line += ']\n' if is_empty_deps else ''
81 indentation_level = None
82
83 if line.strip() not in deleted_sources:
84 fixed_file += line
85
86 with open(filename, 'w') as f:
87 f.write(fixed_file)
88
89 Run(['gn', 'format', filename])
90
91def Rebase(base_path, dependency_path, dependency):
92 base_path = base_path.split(os.path.sep)
93 dependency_path = dependency_path.split(os.path.sep)
94
95 first_difference = None
96 shortest_length = min(len(dependency_path), len(base_path))
97 for i in range(shortest_length):
98 if dependency_path[i] != base_path[i]:
99 first_difference = i
100 break
101
102 first_difference = first_difference or shortest_length
103 base_path = base_path[first_difference:]
104 dependency_path = dependency_path[first_difference:]
105 return (os.path.sep.join((['..'] * len(base_path)) + dependency_path) +
106 ':' + dependency)
107
108def main():
109 deleted_sources = set()
110 errors_by_file = defaultdict(lambda: defaultdict(set))
111
112 with TemporaryDirectory() as tmp_dir:
mbonadei235d5cc2016-12-20 07:19:18 -0800113 mb_script_path = os.path.join(SCRIPT_DIR, 'mb', 'mb.py')
114 mb_config_file_path = os.path.join(SCRIPT_DIR, 'mb', 'mb_config.pyl')
ehmaldonado01653b12016-12-08 07:27:37 -0800115 mb_gen_command = ([
mbonadei235d5cc2016-12-20 07:19:18 -0800116 mb_script_path, 'gen',
ehmaldonado01653b12016-12-08 07:27:37 -0800117 tmp_dir,
mbonadei235d5cc2016-12-20 07:19:18 -0800118 '--config-file', mb_config_file_path,
ehmaldonado01653b12016-12-08 07:27:37 -0800119 ] + sys.argv[1:])
120
121 mb_output = Run(mb_gen_command)
122 errors = mb_output[0].split('ERROR')[1:]
123
124 if mb_output[1]:
125 print mb_output[1]
126 return 1
127
128 for error in errors:
129 error = error.splitlines()
130 target_msg = 'The target:'
131 if target_msg not in error:
132 target_msg = 'It is not in any dependency of'
133 if target_msg not in error:
134 print '\n'.join(error)
135 continue
136 index = error.index(target_msg) + 1
137 path, target = error[index].strip().split(':')
138 if error[index+1] in ('is including a file from the target:',
139 'The include file is in the target(s):'):
140 dep = error[index+2].strip()
141 dep_path, dep = dep.split(':')
142 dep = Rebase(path, dep_path, dep)
143 path = os.path.join(path[2:], 'BUILD.gn')
144 errors_by_file[path][target].add(dep)
145 elif error[index+1] == 'has a source file:':
146 deleted_file = '"' + os.path.basename(error[index+2].strip()) + '",'
147 deleted_sources.add(deleted_file)
148 else:
149 print '\n'.join(error)
150 continue
151
152 for path, missing_deps in errors_by_file.items():
153 FixErrors(path, missing_deps, deleted_sources)
154
155 return 0
156
157if __name__ == '__main__':
158 sys.exit(main())