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