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