3 # Copyright (c) 2011 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.
12 from idl_option
import GetOption
, Option
, ParseOptions
13 from idl_outfile
import IDLOutFile
17 # IDLDiff is a tool for comparing sets of IDL generated header files
18 # with the standard checked in headers. It does this by capturing the
19 # output of the standard diff tool, parsing it into separate changes, then
20 # ignoring changes that are know to be safe, such as adding or removing
24 Option('gen', 'IDL generated files', default
='hdir')
25 Option('src', 'Original ".h" files', default
='../c')
26 Option('halt', 'Stop if a difference is found')
27 Option('diff', 'Directory holding acceptable diffs', default
='diff')
28 Option('ok', 'Write out the diff file.')
31 # A Change object contains the previous lines, new news and change type.
34 def __init__(self
, mode
, was
, now
):
41 print 'Adding %s' % self
.mode
43 print 'Missing %s' % self
.mode
45 print 'Modifying %s' % self
.mode
48 print 'src: >>%s<<' % line
50 print 'gen: >>%s<<' % line
56 # Return True if this change is only a one line change in the copyright notice
57 # such as non-matching years.
59 def IsCopyright(change
):
60 if len(change
.now
) != 1 or len(change
.was
) != 1: return False
61 if 'Copyright (c)' not in change
.now
[0]: return False
62 if 'Copyright (c)' not in change
.was
[0]: return False
68 # Return True if this change only removes a blank line from a comment
70 def IsBlankComment(change
):
71 if change
.now
: return False
72 if len(change
.was
) != 1: return False
73 if change
.was
[0].strip() != '*': return False
79 # Return True if this change only adds or removes blank lines
82 for line
in change
.now
:
84 for line
in change
.was
:
92 # Return True if this change only going from C++ to C style
94 def IsToCppComment(change
):
95 if not len(change
.now
) or len(change
.now
) != len(change
.was
):
97 for index
in range(len(change
.now
)):
98 was
= change
.was
[index
].strip()
101 was
= was
[2:].strip()
102 now
= change
.now
[index
].strip()
105 now
= now
[2:-2].strip()
113 def IsMergeComment(change
):
114 if len(change
.was
) != 1: return False
115 if change
.was
[0].strip() != '*': return False
116 for line
in change
.now
:
117 stripped
= line
.strip()
118 if stripped
!= '*' and stripped
[:2] != '/*' and stripped
[-2:] != '*/':
124 # Return True if this change is only different in the way 'words' are spaced
125 # such as in an enum:
132 def IsSpacing(change
):
133 if len(change
.now
) != len(change
.was
): return False
134 for i
in range(len(change
.now
)):
135 # Also ignore right side comments
137 offs
= line
.find('//')
139 offs
= line
.find('/*')
143 words1
= change
.now
[i
].split()
144 words2
= line
.split()
145 if words1
!= words2
: return False
151 # Return True if change has extra includes
153 def IsInclude(change
):
154 for line
in change
.was
:
155 if line
.strip().find('struct'): return False
156 for line
in change
.now
:
157 if line
and '#include' not in line
: return False
163 # Return True if the change is only missing C++ comments
165 def IsCppComment(change
):
166 if len(change
.now
): return False
167 for line
in change
.was
:
169 if line
[:2] != '//': return False
174 # Return True if none of the changes does not patch an above "bogus" change.
176 def ValidChange(change
):
177 if IsToCppComment(change
): return False
178 if IsCopyright(change
): return False
179 if IsBlankComment(change
): return False
180 if IsMergeComment(change
): return False
181 if IsBlank(change
): return False
182 if IsSpacing(change
): return False
183 if IsInclude(change
): return False
184 if IsCppComment(change
): return False
191 # Check if the combination of last + next change signals they are both
192 # invalid such as swap of line around an invalid block.
194 def Swapped(last
, next
):
195 if not last
.now
and not next
.was
and len(last
.was
) == len(next
.now
):
200 if last
.was
[j
] != next
.now
[(i
+ j
) % cnt
]:
203 if match
: return True
204 if not last
.was
and not next
.now
and len(last
.now
) == len(next
.was
):
209 if last
.now
[i
] != next
.was
[(i
+ j
) % cnt
]:
212 if match
: return True
216 def FilterLinesIn(output
):
220 for index
in range(len(output
)):
223 if len(line
) < 2: continue
225 if line
[2:].strip() == '': continue
226 was
.append((index
, line
[2:]))
228 if line
[2:].strip() == '': continue
229 now
.append((index
, line
[2:]))
230 for windex
, wline
in was
:
231 for nindex
, nline
in now
:
232 if filter[nindex
]: continue
233 if filter[windex
]: continue
235 filter[nindex
] = True
236 filter[windex
] = True
237 if GetOption('verbose'):
238 print "Found %d, %d >>%s<<" % (windex
+ 1, nindex
+ 1, wline
)
240 for index
in range(len(output
)):
241 if not filter[index
]:
242 out
.append(output
[index
])
248 # Parse the output into discrete change blocks.
250 def GetChanges(output
):
251 # Split on lines, adding an END marker to simply add logic
252 lines
= output
.split('\n')
253 lines
= FilterLinesIn(lines
)
263 # print "LINE=%s" % line
264 if not line
: continue
267 if line
[2:].strip() == '': continue
270 words
= line
[2:].split()
271 if len(words
) == 2 and words
[1][-1] == ';':
272 if words
[0] == 'struct' or words
[0] == 'union':
276 if line
[2:].strip() == '': continue
277 if line
[2:10] == '#include': continue
282 change
= Change(line
, was
, now
)
285 if ValidChange(change
):
286 changes
.append(change
)
290 return FilterChanges(changes
)
292 def FilterChanges(changes
):
293 if len(changes
) < 2: return changes
295 filter = [False for change
in changes
]
296 for cur
in range(len(changes
)):
297 for cmp in range(cur
+1, len(changes
)):
300 if Swapped(changes
[cur
], changes
[cmp]):
303 for cur
in range(len(changes
)):
304 if filter[cur
]: continue
305 out
.append(changes
[cur
])
309 filenames
= ParseOptions(args
)
311 gendir
= os
.path
.join(GetOption('gen'), '*.h')
312 filenames
= sorted(glob
.glob(gendir
))
313 srcdir
= os
.path
.join(GetOption('src'), '*.h')
314 srcs
= sorted(glob
.glob(srcdir
))
316 name
= os
.path
.split(name
)[1]
317 name
= os
.path
.join(GetOption('gen'), name
)
318 if name
not in filenames
:
319 print 'Missing: %s' % name
321 for filename
in filenames
:
323 filename
= filename
[len(GetOption('gen')) + 1:]
324 src
= os
.path
.join(GetOption('src'), filename
)
325 diff
= os
.path
.join(GetOption('diff'), filename
)
326 p
= subprocess
.Popen(['diff', src
, gen
], stdout
=subprocess
.PIPE
)
327 output
, errors
= p
.communicate()
330 input = open(diff
, 'rt').read()
335 changes
= GetChanges(output
)
340 print "\n\nDelta between:\n src=%s\n gen=%s\n" % (src
, gen
)
341 for change
in changes
:
343 print 'Done with %s\n\n' % src
345 open(diff
, 'wt').write(output
)
346 if GetOption('halt'):
349 print "\nSAME:\n src=%s\n gen=%s" % (src
, gen
)
350 if input: print ' ** Matched expected diff. **'
353 if __name__
== '__main__':
354 sys
.exit(Main(sys
.argv
[1:]))