2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
11 from idl_option
import GetOption
, Option
, ParseOptions
12 from idl_outfile
import IDLOutFile
16 # IDLDiff is a tool for comparing sets of IDL generated header files
17 # with the standard checked in headers. It does this by capturing the
18 # output of the standard diff tool, parsing it into separate changes, then
19 # ignoring changes that are know to be safe, such as adding or removing
23 Option('gen', 'IDL generated files', default
='hdir')
24 Option('src', 'Original ".h" files', default
='../c')
25 Option('halt', 'Stop if a difference is found')
26 Option('diff', 'Directory holding acceptable diffs', default
='diff')
27 Option('ok', 'Write out the diff file.')
30 # A Change object contains the previous lines, new news and change type.
33 def __init__(self
, mode
, was
, now
):
40 print 'Adding %s' % self
.mode
42 print 'Missing %s' % self
.mode
44 print 'Modifying %s' % self
.mode
47 print 'src: >>%s<<' % line
49 print 'gen: >>%s<<' % line
55 # Return True if this change is only a one line change in the copyright notice
56 # such as non-matching years.
58 def IsCopyright(change
):
59 if len(change
.now
) != 1 or len(change
.was
) != 1: return False
60 if 'Copyright (c)' not in change
.now
[0]: return False
61 if 'Copyright (c)' not in change
.was
[0]: return False
67 # Return True if this change only removes a blank line from a comment
69 def IsBlankComment(change
):
70 if change
.now
: return False
71 if len(change
.was
) != 1: return False
72 if change
.was
[0].strip() != '*': return False
78 # Return True if this change only adds or removes blank lines
81 for line
in change
.now
:
83 for line
in change
.was
:
91 # Return True if this change only going from C++ to C style
93 def IsToCppComment(change
):
94 if not len(change
.now
) or len(change
.now
) != len(change
.was
):
96 for index
in range(len(change
.now
)):
97 was
= change
.was
[index
].strip()
100 was
= was
[2:].strip()
101 now
= change
.now
[index
].strip()
104 now
= now
[2:-2].strip()
112 def IsMergeComment(change
):
113 if len(change
.was
) != 1: return False
114 if change
.was
[0].strip() != '*': return False
115 for line
in change
.now
:
116 stripped
= line
.strip()
117 if stripped
!= '*' and stripped
[:2] != '/*' and stripped
[-2:] != '*/':
123 # Return True if this change is only different in the way 'words' are spaced
124 # such as in an enum:
131 def IsSpacing(change
):
132 if len(change
.now
) != len(change
.was
): return False
133 for i
in range(len(change
.now
)):
134 # Also ignore right side comments
136 offs
= line
.find('//')
138 offs
= line
.find('/*')
142 words1
= change
.now
[i
].split()
143 words2
= line
.split()
144 if words1
!= words2
: return False
150 # Return True if change has extra includes
152 def IsInclude(change
):
153 for line
in change
.was
:
154 if line
.strip().find('struct'): return False
155 for line
in change
.now
:
156 if line
and '#include' not in line
: return False
162 # Return True if the change is only missing C++ comments
164 def IsCppComment(change
):
165 if len(change
.now
): return False
166 for line
in change
.was
:
168 if line
[:2] != '//': return False
173 # Return True if none of the changes does not patch an above "bogus" change.
175 def ValidChange(change
):
176 if IsToCppComment(change
): return False
177 if IsCopyright(change
): return False
178 if IsBlankComment(change
): return False
179 if IsMergeComment(change
): return False
180 if IsBlank(change
): return False
181 if IsSpacing(change
): return False
182 if IsInclude(change
): return False
183 if IsCppComment(change
): return False
190 # Check if the combination of last + next change signals they are both
191 # invalid such as swap of line around an invalid block.
193 def Swapped(last
, next
):
194 if not last
.now
and not next
.was
and len(last
.was
) == len(next
.now
):
199 if last
.was
[j
] != next
.now
[(i
+ j
) % cnt
]:
202 if match
: return True
203 if not last
.was
and not next
.now
and len(last
.now
) == len(next
.was
):
208 if last
.now
[i
] != next
.was
[(i
+ j
) % cnt
]:
211 if match
: return True
215 def FilterLinesIn(output
):
219 for index
in range(len(output
)):
222 if len(line
) < 2: continue
224 if line
[2:].strip() == '': continue
225 was
.append((index
, line
[2:]))
227 if line
[2:].strip() == '': continue
228 now
.append((index
, line
[2:]))
229 for windex
, wline
in was
:
230 for nindex
, nline
in now
:
231 if filter[nindex
]: continue
232 if filter[windex
]: continue
234 filter[nindex
] = True
235 filter[windex
] = True
236 if GetOption('verbose'):
237 print "Found %d, %d >>%s<<" % (windex
+ 1, nindex
+ 1, wline
)
239 for index
in range(len(output
)):
240 if not filter[index
]:
241 out
.append(output
[index
])
247 # Parse the output into discrete change blocks.
249 def GetChanges(output
):
250 # Split on lines, adding an END marker to simply add logic
251 lines
= output
.split('\n')
252 lines
= FilterLinesIn(lines
)
262 # print "LINE=%s" % line
263 if not line
: continue
266 if line
[2:].strip() == '': continue
269 words
= line
[2:].split()
270 if len(words
) == 2 and words
[1][-1] == ';':
271 if words
[0] == 'struct' or words
[0] == 'union':
275 if line
[2:].strip() == '': continue
276 if line
[2:10] == '#include': continue
281 change
= Change(line
, was
, now
)
284 if ValidChange(change
):
285 changes
.append(change
)
289 return FilterChanges(changes
)
291 def FilterChanges(changes
):
292 if len(changes
) < 2: return changes
294 filter = [False for change
in changes
]
295 for cur
in range(len(changes
)):
296 for cmp in range(cur
+1, len(changes
)):
299 if Swapped(changes
[cur
], changes
[cmp]):
302 for cur
in range(len(changes
)):
303 if filter[cur
]: continue
304 out
.append(changes
[cur
])
308 filenames
= ParseOptions(args
)
310 gendir
= os
.path
.join(GetOption('gen'), '*.h')
311 filenames
= sorted(glob
.glob(gendir
))
312 srcdir
= os
.path
.join(GetOption('src'), '*.h')
313 srcs
= sorted(glob
.glob(srcdir
))
315 name
= os
.path
.split(name
)[1]
316 name
= os
.path
.join(GetOption('gen'), name
)
317 if name
not in filenames
:
318 print 'Missing: %s' % name
320 for filename
in filenames
:
322 filename
= filename
[len(GetOption('gen')) + 1:]
323 src
= os
.path
.join(GetOption('src'), filename
)
324 diff
= os
.path
.join(GetOption('diff'), filename
)
325 p
= subprocess
.Popen(['diff', src
, gen
], stdout
=subprocess
.PIPE
)
326 output
, errors
= p
.communicate()
329 input = open(diff
, 'rt').read()
334 changes
= GetChanges(output
)
339 print "\n\nDelta between:\n src=%s\n gen=%s\n" % (src
, gen
)
340 for change
in changes
:
342 print 'Done with %s\n\n' % src
344 open(diff
, 'wt').write(output
)
345 if GetOption('halt'):
348 print "\nSAME:\n src=%s\n gen=%s" % (src
, gen
)
349 if input: print ' ** Matched expected diff. **'
353 if __name__
== '__main__':
354 sys
.exit(Main(sys
.argv
[1:]))