Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / tools / gn / bin / gyp_flag_compare.py
1 #!/usr/bin/env python
2
3 # Copyright 2014 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
6
7 """Given the output of -t commands from a ninja build for a gyp and GN generated
8 build, report on differences between the command lines."""
9
10
11 import os
12 import shlex
13 import subprocess
14 import sys
15
16
17 # Must be in src/.
18 os.chdir(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
19
20
21 g_total_differences = 0
22
23
24 def FindAndRemoveArgWithValue(command_line, argname):
25   """Given a command line as a list, remove and return the value of an option
26   that takes a value as a separate entry.
27
28   Modifies |command_line| in place.
29   """
30   if argname not in command_line:
31     return ''
32   location = command_line.index(argname)
33   value = command_line[location + 1]
34   command_line[location:location + 2] = []
35   return value
36
37
38 def MergeSpacedArgs(command_line, argname):
39   """Combine all arguments |argname| with their values, separated by a space."""
40   i = 0
41   result = []
42   while i < len(command_line):
43     arg = command_line[i]
44     if arg == argname:
45       result.append(arg + ' ' + command_line[i + 1])
46       i += 1
47     else:
48       result.append(arg)
49     i += 1
50   return result
51
52
53 def NormalizeSymbolArguments(command_line):
54   """Normalize -g arguments.
55
56   If there's no -g args, it's equivalent to -g0. -g2 is equivalent to -g.
57   Modifies |command_line| in place.
58   """
59   # Strip -g0 if there's no symbols.
60   have_some_symbols = False
61   for x in command_line:
62     if x.startswith('-g') and x != '-g0':
63       have_some_symbols = True
64   if not have_some_symbols and '-g0' in command_line:
65     command_line.remove('-g0')
66
67   # Rename -g2 to -g.
68   if '-g2' in command_line:
69     command_line[index('-g2')] = '-g'
70
71
72 def GetFlags(lines):
73   """Turn a list of command lines into a semi-structured dict."""
74   flags_by_output = {}
75   for line in lines:
76     # TODO(scottmg): Hacky way of getting only cc for now.
77     if 'clang' not in line:
78       continue
79
80     command_line = shlex.split(line.strip())[1:]
81
82     output_name = FindAndRemoveArgWithValue(command_line, '-o')
83     dep_name = FindAndRemoveArgWithValue(command_line, '-MF')
84
85     NormalizeSymbolArguments(command_line)
86
87     command_line = MergeSpacedArgs(command_line, '-Xclang')
88
89     defines = [x for x in command_line if x.startswith('-D')]
90     include_dirs = [x for x in command_line if x.startswith('-I')]
91     dash_f = [x for x in command_line if x.startswith('-f')]
92     warnings = [x for x in command_line if x.startswith('-W')]
93     cc_file = [x for x in command_line if x.endswith('.cc') or
94                                           x.endswith('.c') or
95                                           x.endswith('.cpp')]
96     if len(cc_file) != 1:
97       print 'Skipping %s' % command_line
98       continue
99     assert len(cc_file) == 1
100     others = [x for x in command_line if x not in defines and \
101                                          x not in include_dirs and \
102                                          x not in dash_f and \
103                                          x not in warnings and \
104                                          x not in cc_file]
105
106     # Filter for libFindBadConstructs.so having a relative path in one and
107     # absolute path in the other.
108     others_filtered = []
109     for x in others:
110       if x.startswith('-Xclang ') and x.endswith('libFindBadConstructs.so'):
111         others_filtered.append(
112             '-Xclang ' +
113             os.path.join(os.getcwd(),
114                          os.path.normpath(
115                              os.path.join('out/gn_flags', x.split(' ', 1)[1]))))
116       elif x.startswith('-B'):
117         others_filtered.append(
118             '-B' +
119             os.path.join(os.getcwd(),
120                          os.path.normpath(os.path.join('out/gn_flags', x[2:]))))
121       else:
122         others_filtered.append(x)
123     others = others_filtered
124
125     flags_by_output[cc_file[0]] = {
126       'output': output_name,
127       'depname': dep_name,
128       'defines': sorted(defines),
129       'include_dirs': sorted(include_dirs),  # TODO(scottmg): This is wrong.
130       'dash_f': sorted(dash_f),
131       'warnings': sorted(warnings),
132       'other': sorted(others),
133     }
134   return flags_by_output
135
136
137 def CompareLists(gyp, gn, name, dont_care_gyp=None, dont_care_gn=None):
138   """Return a report of any differences between gyp and gn lists, ignoring
139   anything in |dont_care_{gyp|gn}| respectively."""
140   global g_total_differences
141   if not dont_care_gyp:
142     dont_care_gyp = []
143   if not dont_care_gn:
144     dont_care_gn = []
145   output = ''
146   if gyp[name] != gn[name]:
147     gyp_set = set(gyp[name])
148     gn_set = set(gn[name])
149     missing_in_gyp = gyp_set - gn_set
150     missing_in_gn = gn_set - gyp_set
151     missing_in_gyp -= set(dont_care_gyp)
152     missing_in_gn -= set(dont_care_gn)
153     if missing_in_gyp or missing_in_gn:
154       output += '  %s differ:\n' % name
155     if missing_in_gyp:
156       output += '    In gyp, but not in GN:\n      %s' % '\n      '.join(
157           sorted(missing_in_gyp)) + '\n'
158       g_total_differences += len(missing_in_gyp)
159     if missing_in_gn:
160       output += '    In GN, but not in gyp:\n      %s' % '\n      '.join(
161           sorted(missing_in_gn)) + '\n\n'
162       g_total_differences += len(missing_in_gn)
163   return output
164
165
166 def Run(command_line):
167   """Run |command_line| as a subprocess and return stdout. Raises on error."""
168   return subprocess.check_output(command_line, shell=True)
169
170
171 def main():
172   if len(sys.argv) != 2 and len(sys.argv) != 3:
173     print 'usage: %s gyp_target gn_target' % __file__
174     print '   or: %s target' % __file__
175     return 1
176
177   if len(sys.argv) == 2:
178     sys.argv.append(sys.argv[1])
179
180   print >>sys.stderr, 'Regenerating...'
181   # Currently only Release, non-component.
182   Run('gn gen out/gn_flags --args="is_debug=false is_component_build=false"')
183   os.environ.pop('GYP_DEFINES', None)
184   Run('python build/gyp_chromium -Goutput_dir=out_gyp_flags -Gconfig=Release')
185   gn = Run('ninja -C out/gn_flags -t commands %s' % sys.argv[2])
186   gyp = Run('ninja -C out_gyp_flags/Release -t commands %s' % sys.argv[1])
187   all_gyp_flags = GetFlags(gyp.splitlines())
188   all_gn_flags = GetFlags(gn.splitlines())
189   gyp_files = set(all_gyp_flags.keys())
190   gn_files = set(all_gn_flags.keys())
191   different_source_list = gyp_files != gn_files
192   if different_source_list:
193     print 'Different set of sources files:'
194     print '  In gyp, not in GN:\n    %s' % '\n    '.join(
195         sorted(gyp_files - gn_files))
196     print '  In GN, not in gyp:\n    %s' % '\n    '.join(
197         sorted(gn_files - gyp_files))
198     print '\nNote that flags will only be compared for files in both sets.\n'
199   file_list = gyp_files & gn_files
200   files_with_given_differences = {}
201   for filename in sorted(file_list):
202     gyp_flags = all_gyp_flags[filename]
203     gn_flags = all_gn_flags[filename]
204     differences = CompareLists(gyp_flags, gn_flags, 'dash_f')
205     differences += CompareLists(gyp_flags, gn_flags, 'defines')
206     differences += CompareLists(gyp_flags, gn_flags, 'include_dirs')
207     differences += CompareLists(gyp_flags, gn_flags, 'warnings', dont_care_gn=[
208         # More conservative warnings in GN we consider to be OK.
209         '-Wendif-labels',
210         '-Wextra',
211         '-Wsign-compare',
212         ])
213     differences += CompareLists(gyp_flags, gn_flags, 'other')
214     if differences:
215       files_with_given_differences.setdefault(differences, []).append(filename)
216
217   for diff, files in files_with_given_differences.iteritems():
218     print '\n'.join(sorted(files))
219     print diff
220
221   print 'Total differences:', g_total_differences
222   # TODO(scottmg): Return failure on difference once we're closer to identical.
223   return 0
224
225
226 if __name__ == '__main__':
227   sys.exit(main())