Roll src/third_party/WebKit 28cb7a5:eda0012 (svn 190243:190250)
[chromium-blink-merge.git] / ppapi / generators / idl_outfile.py
blobf6a16275ccecb89e2d5e08c5b766a81190c18d3f
1 #!/usr/bin/env python
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.
6 """ Output file objects for generator. """
8 import difflib
9 import os
10 import time
11 import subprocess
12 import sys
14 from idl_log import ErrOut, InfoOut, WarnOut
15 from idl_option import GetOption, Option, ParseOptions
16 from stat import *
18 Option('diff', 'Generate a DIFF when saving the file.')
22 # IDLOutFile
24 # IDLOutFile provides a temporary output file. By default, the object will
25 # not write the output if the file already exists, and matches what will be
26 # written. This prevents the timestamp from changing to optimize cases where
27 # the output files are used by a timestamp dependent build system
29 class IDLOutFile(object):
30 def __init__(self, filename, always_write = False, create_dir = True):
31 self.filename = filename
32 self.always_write = always_write
33 self.create_dir = create_dir
34 self.outlist = []
35 self.open = True
37 # Compare the old text to the current list of output lines.
38 def IsEquivalent_(self, oldtext):
39 if not oldtext: return False
41 oldlines = oldtext.split('\n')
42 curlines = (''.join(self.outlist)).split('\n')
44 # If number of lines don't match, it's a mismatch
45 if len(oldlines) != len(curlines):
46 return False
48 for index in range(len(oldlines)):
49 oldline = oldlines[index]
50 curline = curlines[index]
52 if oldline == curline: continue
54 curwords = curline.split()
55 oldwords = oldline.split()
57 # It wasn't a perfect match. Check for changes we should ignore.
58 # Unmatched lines must be the same length
59 if len(curwords) != len(oldwords):
60 return False
62 # If it's not a comment then it's a mismatch
63 if curwords[0] not in ['*', '/*', '//']:
64 return False
66 # Ignore changes to the Copyright year which is autogenerated
67 # /* Copyright (c) 2011 The Chromium Authors. All rights reserved.
68 if len(curwords) > 4 and curwords[1] == 'Copyright':
69 if curwords[4:] == oldwords[4:]: continue
71 # Ignore changes to auto generation timestamp.
72 # // From FILENAME.idl modified DAY MON DATE TIME YEAR.
73 # /* From FILENAME.idl modified DAY MON DATE TIME YEAR. */
74 # The line may be wrapped, so first deal with the first "From" line.
75 if curwords[1] == 'From':
76 if curwords[0:4] == oldwords[0:4]: continue
78 # Ignore changes to auto generation timestamp when line is wrapped
79 if index > 0:
80 two_line_oldwords = oldlines[index - 1].split() + oldwords[1:]
81 two_line_curwords = curlines[index - 1].split() + curwords[1:]
82 if len(two_line_curwords) > 8 and two_line_curwords[1] == 'From':
83 if two_line_curwords[0:4] == two_line_oldwords[0:4]: continue
85 return False
86 return True
88 # Return the file name
89 def Filename(self):
90 return self.filename
92 # Append to the output if the file is still open
93 def Write(self, string):
94 if not self.open:
95 raise RuntimeError('Could not write to closed file %s.' % self.filename)
96 self.outlist.append(string)
98 # Run clang-format on the buffered file contents.
99 def ClangFormat(self):
100 clang_format = subprocess.Popen(['clang-format', '-style=Chromium'],
101 stdin=subprocess.PIPE,
102 stdout=subprocess.PIPE)
103 new_output = clang_format.communicate("".join(self.outlist))[0]
104 self.outlist = [new_output]
106 # Close the file, flushing it to disk
107 def Close(self):
108 filename = os.path.realpath(self.filename)
109 self.open = False
110 outtext = ''.join(self.outlist)
111 oldtext = ''
113 if not self.always_write:
114 if os.path.isfile(filename):
115 oldtext = open(filename, 'rb').read()
116 if self.IsEquivalent_(oldtext):
117 if GetOption('verbose'):
118 InfoOut.Log('Output %s unchanged.' % self.filename)
119 return False
121 if GetOption('diff'):
122 for line in difflib.unified_diff(oldtext.split('\n'), outtext.split('\n'),
123 'OLD ' + self.filename,
124 'NEW ' + self.filename,
125 n=1, lineterm=''):
126 ErrOut.Log(line)
128 try:
129 # If the directory does not exit, try to create it, if we fail, we
130 # still get the exception when the file is openned.
131 basepath, leafname = os.path.split(filename)
132 if basepath and not os.path.isdir(basepath) and self.create_dir:
133 InfoOut.Log('Creating directory: %s\n' % basepath)
134 os.makedirs(basepath)
136 if not GetOption('test'):
137 outfile = open(filename, 'wb')
138 outfile.write(outtext)
139 outfile.close();
140 InfoOut.Log('Output %s written.' % self.filename)
141 return True
143 except IOError as (errno, strerror):
144 ErrOut.Log("I/O error(%d): %s" % (errno, strerror))
145 except:
146 ErrOut.Log("Unexpected error: %s" % sys.exc_info()[0])
147 raise
149 return False
152 def TestFile(name, stringlist, force, update):
153 errors = 0
155 # Get the old timestamp
156 if os.path.exists(name):
157 old_time = os.stat(filename)[ST_MTIME]
158 else:
159 old_time = 'NONE'
161 # Create the file and write to it
162 out = IDLOutFile(filename, force)
163 for item in stringlist:
164 out.Write(item)
166 # We wait for flush to force the timestamp to change
167 time.sleep(2)
169 wrote = out.Close()
170 cur_time = os.stat(filename)[ST_MTIME]
171 if update:
172 if not wrote:
173 ErrOut.Log('Failed to write output %s.' % filename)
174 return 1
175 if cur_time == old_time:
176 ErrOut.Log('Failed to update timestamp for %s.' % filename)
177 return 1
178 else:
179 if wrote:
180 ErrOut.Log('Should not have writen output %s.' % filename)
181 return 1
182 if cur_time != old_time:
183 ErrOut.Log('Should not have modified timestamp for %s.' % filename)
184 return 1
185 return 0
188 def main():
189 errors = 0
190 stringlist = ['Test', 'Testing\n', 'Test']
191 filename = 'outtest.txt'
193 # Test forcibly writing a file
194 errors += TestFile(filename, stringlist, force=True, update=True)
196 # Test conditionally writing the file skipping
197 errors += TestFile(filename, stringlist, force=False, update=False)
199 # Test conditionally writing the file updating
200 errors += TestFile(filename, stringlist + ['X'], force=False, update=True)
202 # Clean up file
203 os.remove(filename)
204 if not errors: InfoOut.Log('All tests pass.')
205 return errors
208 if __name__ == '__main__':
209 sys.exit(main())