idl_outfile.py revision 5821806d5e7f356e8fa4b058a389a808ea183019
136b56886974eae4f9c5ebc96befd3e7bfe5de338Stephen Hines#!/usr/bin/env python
20a08460599eed603e469e3e16d0cf6aa33b8ba93Chandler Carruth# Copyright (c) 2012 The Chromium Authors. All rights reserved.
30a08460599eed603e469e3e16d0cf6aa33b8ba93Chandler Carruth# Use of this source code is governed by a BSD-style license that can be
40a08460599eed603e469e3e16d0cf6aa33b8ba93Chandler Carruth# found in the LICENSE file.
50a08460599eed603e469e3e16d0cf6aa33b8ba93Chandler Carruth
6e3ba15c794839abe076e3e2bdf6c626396a19d4dWill Dietz""" Output file objects for generator. """
731c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
831c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaarimport difflib
94ca7e09b7c1e41535f2a1bd86915375d023daf27Chandler Carruthimport os
1031c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaarimport time
1131c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaarimport sys
1231c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
1331c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaarfrom idl_log import ErrOut, InfoOut, WarnOut
1431c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaarfrom idl_option import GetOption, Option, ParseOptions
1531c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaarfrom stat import *
1631c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
1731c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick TryzelaarOption('diff', 'Generate a DIFF when saving the file.')
1831c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
1931c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaardef IsEquivelent(intext, outtext):
2031c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  if not intext: return False
2131c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  inlines = intext.split('\n')
2231c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  outlines = outtext.split('\n')
2331c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
2431c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  # If number of lines don't match, it's a mismatch
2531c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  if len(inlines) != len(outlines):  return False
2631c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
2731c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  for index in range(len(inlines)):
2831c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    inline = inlines[index]
2931c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    outline = outlines[index]
3031c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
3131c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    if inline == outline: continue
3231c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
3331c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    # If the line is not an exact match, check for comment deltas
3431c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    inwords = inline.split()
3531c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    outwords = outline.split()
3631c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
3731c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    if not inwords or not outwords: return False
3831c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    if inwords[0] != outwords[0] or inwords[0] not in ('/*', '*'): return False
3931c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
4031c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    # Neither the year, nor the modified date need an exact match
4131c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    if inwords[1] == 'Copyright':
4231c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      if inwords[4:] == outwords[4:]: continue
4331c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    elif inwords[1] == 'From': # Un-wrapped modified date.
4431c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      if inwords[0:4] == outwords[0:4]: continue
4531c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    elif inwords[1] == 'modified': # Wrapped modified date.
4631c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      if inwords[0:2] == outwords[0:2]: continue
4731c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    return False
4831c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  return True
4931c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
5031c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
5131c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar#
5231c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar# IDLOutFile
5331c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar#
5431c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar# IDLOutFile provides a temporary output file.  By default, the object will
5531c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar# not write the output if the file already exists, and matches what will be
5631c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar# written.  This prevents the timestamp from changing to optimize cases where
5731c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar# the output files are used by a timestamp dependent build system
5831c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar#
5931c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaarclass IDLOutFile(object):
6031c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  def __init__(self, filename, always_write = False, create_dir = True):
6131c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    self.filename = filename
6231c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    self.always_write = always_write
6331c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    self.create_dir = create_dir
6431c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    self.outlist = []
6531c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    self.open = True
6631c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
6731c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  # Return the file name
6831c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  def Filename(self):
6931c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    return self.filename
7031c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
7131c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  # Append to the output if the file is still open
7231c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  def Write(self, string):
7331c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    if not self.open:
7431c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      raise RuntimeError('Could not write to closed file %s.' % self.filename)
7531c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    self.outlist.append(string)
7631c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
7731c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  # Close the file
7831c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  def Close(self):
7931c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    filename = os.path.realpath(self.filename)
8031c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    self.open = False
8131c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    outtext = ''.join(self.outlist)
8231c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    if not self.always_write:
839d7c776d32c8a4d64b37a91c2d627629cf1498efBill Wendling      if os.path.isfile(filename):
8431c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar        intext = open(filename, 'rb').read()
8531c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      else:
8631c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar        intext = ''
879d7c776d32c8a4d64b37a91c2d627629cf1498efBill Wendling
8831c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      if IsEquivelent(intext, outtext):
8931c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar        if GetOption('verbose'):
9031c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar          InfoOut.Log('Output %s unchanged.' % self.filename)
9131c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar        return False
9231c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
9331c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    if GetOption('diff'):
9431c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      for line in difflib.unified_diff(intext.split('\n'), outtext.split('\n'),
9531c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar                                       'OLD ' + self.filename,
9631c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar                                       'NEW ' + self.filename,
9731c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar                                       n=1, lineterm=''):
9831c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar        ErrOut.Log(line)
9931c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
10031c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    try:
10131c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      # If the directory does not exit, try to create it, if we fail, we
10231c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      # still get the exception when the file is openned.
10331c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      basepath, leafname = os.path.split(filename)
10431c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      if basepath and not os.path.isdir(basepath) and self.create_dir:
10531c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar        InfoOut.Log('Creating directory: %s\n' % basepath)
10631c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar        os.makedirs(basepath)
10731c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
10831c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      if not GetOption('test'):
10931c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar        outfile = open(filename, 'wb')
11031c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar        outfile.write(outtext)
11131c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar        InfoOut.Log('Output %s written.' % self.filename)
11231c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      return True
11331c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
11431c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    except IOError as (errno, strerror):
11531c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      ErrOut.Log("I/O error(%d): %s" % (errno, strerror))
11631c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    except:
11731c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      ErrOut.Log("Unexpected error: %s" % sys.exc_info()[0])
11831c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      raise
11931c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
12031c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    return False
12131c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
12231c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
12331c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaardef TestFile(name, stringlist, force, update):
12431c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  errors = 0
12531c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
12631c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  # Get the old timestamp
12731c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  if os.path.exists(name):
12831c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    old_time = os.stat(filename)[ST_MTIME]
12931c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  else:
13031c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    old_time = 'NONE'
13131c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
13231c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  # Create the file and write to it
13331c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  out = IDLOutFile(filename, force)
13431c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  for item in stringlist:
13531c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    out.Write(item)
13631c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
13731c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  # We wait for flush to force the timestamp to change
13831c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  time.sleep(2)
13931c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
14031c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  wrote = out.Close()
14131c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  cur_time = os.stat(filename)[ST_MTIME]
14231c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  if update:
14331c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    if not wrote:
14431c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      ErrOut.Log('Failed to write output %s.' % filename)
14531c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      return 1
14631c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    if cur_time == old_time:
14731c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      ErrOut.Log('Failed to update timestamp for %s.' % filename)
14831c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      return 1
14931c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  else:
1509d7c776d32c8a4d64b37a91c2d627629cf1498efBill Wendling    if wrote:
15131c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      ErrOut.Log('Should not have writen output %s.' % filename)
15231c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      return 1
15331c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar    if cur_time != old_time:
15431c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      ErrOut.Log('Should not have modified timestamp for %s.' % filename)
15531c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar      return 1
15631c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  return 0
15731c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
15831c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
15931c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaardef main():
16031c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  errors = 0
16131c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  stringlist = ['Test', 'Testing\n', 'Test']
16231c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  filename = 'outtest.txt'
16331c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
16431c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  # Test forcibly writing a file
16531c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  errors += TestFile(filename, stringlist, force=True, update=True)
16631c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
16731c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  # Test conditionally writing the file skipping
16831c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  errors += TestFile(filename, stringlist, force=False, update=False)
16931c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
17031c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  # Test conditionally writing the file updating
17131c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  errors += TestFile(filename, stringlist + ['X'], force=False, update=True)
17231c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
17331c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  # Clean up file
17431c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  os.remove(filename)
17531c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  if not errors: InfoOut.Log('All tests pass.')
17631c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  return errors
17731c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
17831c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar
17931c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaarif __name__ == '__main__':
18031c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar  sys.exit(main())
18131c6c5d58a6d2254063e8a18fd32b851a06e2ddfErick Tryzelaar