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