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. """
14 from idl_log
import ErrOut
, InfoOut
, WarnOut
15 from idl_option
import GetOption
, Option
, ParseOptions
18 Option('diff', 'Generate a DIFF when saving the file.')
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
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
):
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
):
62 # If it's not a comment then it's a mismatch
63 if curwords
[0] not in ['*', '/*', '//']:
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
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
88 # Return the file name
92 # Append to the output if the file is still open
93 def Write(self
, string
):
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
108 filename
= os
.path
.realpath(self
.filename
)
110 outtext
= ''.join(self
.outlist
)
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
)
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
,
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
)
140 InfoOut
.Log('Output %s written.' % self
.filename
)
143 except IOError as (errno
, strerror
):
144 ErrOut
.Log("I/O error(%d): %s" % (errno
, strerror
))
146 ErrOut
.Log("Unexpected error: %s" % sys
.exc_info()[0])
152 def TestFile(name
, stringlist
, force
, update
):
155 # Get the old timestamp
156 if os
.path
.exists(name
):
157 old_time
= os
.stat(filename
)[ST_MTIME
]
161 # Create the file and write to it
162 out
= IDLOutFile(filename
, force
)
163 for item
in stringlist
:
166 # We wait for flush to force the timestamp to change
170 cur_time
= os
.stat(filename
)[ST_MTIME
]
173 ErrOut
.Log('Failed to write output %s.' % filename
)
175 if cur_time
== old_time
:
176 ErrOut
.Log('Failed to update timestamp for %s.' % filename
)
180 ErrOut
.Log('Should not have writen output %s.' % filename
)
182 if cur_time
!= old_time
:
183 ErrOut
.Log('Should not have modified timestamp for %s.' % filename
)
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)
204 if not errors
: InfoOut
.Log('All tests pass.')
208 if __name__
== '__main__':