Home | History | Annotate | Download | only in generators
      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 
      8 import difflib
      9 import os
     10 import time
     11 import sys
     12 
     13 from idl_log import ErrOut, InfoOut, WarnOut
     14 from idl_option import GetOption, Option, ParseOptions
     15 from stat import *
     16 
     17 Option('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 #
     28 class 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 
    138 def 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 
    174 def 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 
    194 if __name__ == '__main__':
    195   sys.exit(main())
    196