#!/usr/bin/env python # Copyright (c) 2012 The Chromium Authors. All rights reserved. # 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 (c) 2011 The Chromium Authors. All rights reserved. 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): oldtext = open(filename, 'rb').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'): outfile = open(filename, 'wb') outfile.write(outtext) outfile.close(); InfoOut.Log('Output %s written.' % self.filename) return True except IOError as (errno, strerror): ErrOut.Log("I/O error(%d): %s" % (errno, 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())