OLD | NEW |
---|---|
1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import difflib | |
5 import hashlib | 6 import hashlib |
6 import os | 7 import os |
8 import re | |
9 | |
10 | |
11 # When set and a difference is detected, a diff of what changed is printed. | |
12 _PRINT_MD5_DIFFS = int(os.environ.get('PRINT_MD5_DIFFS', 0)) | |
13 | |
14 # Used to strip off temp dir prefix. | |
15 _TEMP_DIR_PATTERN = re.compile(r'^/tmp/.*?/') | |
7 | 16 |
8 | 17 |
9 def CallAndRecordIfStale( | 18 def CallAndRecordIfStale( |
10 function, record_path=None, input_paths=None, input_strings=None, | 19 function, record_path=None, input_paths=None, input_strings=None, |
11 force=False): | 20 force=False): |
12 """Calls function if the md5sum of the input paths/strings has changed. | 21 """Calls function if the md5sum of the input paths/strings has changed. |
13 | 22 |
14 The md5sum of the inputs is compared with the one stored in record_path. If | 23 The md5sum of the inputs is compared with the one stored in record_path. If |
15 this has changed (or the record doesn't exist), function will be called and | 24 this has changed (or the record doesn't exist), function will be called and |
16 the new md5sum will be recorded. | 25 the new md5sum will be recorded. |
17 | 26 |
18 If force is True, the function will be called regardless of whether the | 27 If force is True, the function will be called regardless of whether the |
19 md5sum is out of date. | 28 md5sum is out of date. |
20 """ | 29 """ |
21 if not input_paths: | 30 if not input_paths: |
22 input_paths = [] | 31 input_paths = [] |
23 if not input_strings: | 32 if not input_strings: |
24 input_strings = [] | 33 input_strings = [] |
25 md5_checker = _Md5Checker( | 34 md5_checker = _Md5Checker( |
26 record_path=record_path, | 35 record_path=record_path, |
27 input_paths=input_paths, | 36 input_paths=input_paths, |
28 input_strings=input_strings) | 37 input_strings=input_strings) |
29 if force or md5_checker.IsStale(): | 38 |
39 is_stale = md5_checker.old_digest != md5_checker.new_digest | |
40 if force or is_stale: | |
41 if is_stale and _PRINT_MD5_DIFFS: | |
42 import sys | |
jbudorick
2015/09/02 01:13:50
I would rather avoid local imports. I don't know o
agrieve
2015/09/02 15:01:08
Done.
| |
43 from util import build_utils | |
44 if build_utils.COLORAMA_ROOT not in sys.path: | |
45 sys.path.append(build_utils.COLORAMA_ROOT) | |
46 import colorama | |
47 | |
48 print '%sDifference found in %s:%s' % ( | |
49 colorama.Fore.YELLOW, record_path, colorama.Fore.RESET) | |
50 print md5_checker.DescribeDifference() | |
30 function() | 51 function() |
31 md5_checker.Write() | 52 md5_checker.Write() |
32 | 53 |
33 | 54 |
34 def _UpdateMd5ForFile(md5, path, block_size=2**16): | 55 def _UpdateMd5ForFile(md5, path, block_size=2**16): |
35 with open(path, 'rb') as infile: | 56 with open(path, 'rb') as infile: |
36 while True: | 57 while True: |
37 data = infile.read(block_size) | 58 data = infile.read(block_size) |
38 if not data: | 59 if not data: |
39 break | 60 break |
40 md5.update(data) | 61 md5.update(data) |
41 | 62 |
42 | 63 |
43 def _UpdateMd5ForDirectory(md5, dir_path): | 64 def _UpdateMd5ForDirectory(md5, dir_path): |
44 for root, _, files in os.walk(dir_path): | 65 for root, _, files in os.walk(dir_path): |
45 for f in files: | 66 for f in files: |
46 _UpdateMd5ForFile(md5, os.path.join(root, f)) | 67 _UpdateMd5ForFile(md5, os.path.join(root, f)) |
47 | 68 |
48 | 69 |
49 def _UpdateMd5ForPath(md5, path): | 70 def _UpdateMd5ForPath(md5, path): |
50 if os.path.isdir(path): | 71 if os.path.isdir(path): |
51 _UpdateMd5ForDirectory(md5, path) | 72 _UpdateMd5ForDirectory(md5, path) |
52 else: | 73 else: |
53 _UpdateMd5ForFile(md5, path) | 74 _UpdateMd5ForFile(md5, path) |
54 | 75 |
55 | 76 |
77 def _TrimPathPrefix(path): | |
78 """Attempts to remove temp dir prefix from the path. | |
79 | |
80 Use this only for extended_info (not for the actual md5). | |
81 """ | |
82 return _TEMP_DIR_PATTERN.sub('{TMP}', path) | |
83 | |
84 | |
56 class _Md5Checker(object): | 85 class _Md5Checker(object): |
57 def __init__(self, record_path=None, input_paths=None, input_strings=None): | 86 def __init__(self, record_path=None, input_paths=None, input_strings=None): |
58 if not input_paths: | 87 if not input_paths: |
59 input_paths = [] | 88 input_paths = [] |
60 if not input_strings: | 89 if not input_strings: |
61 input_strings = [] | 90 input_strings = [] |
62 | 91 |
63 assert record_path.endswith('.stamp'), ( | 92 assert record_path.endswith('.stamp'), ( |
64 'record paths must end in \'.stamp\' so that they are easy to find ' | 93 'record paths must end in \'.stamp\' so that they are easy to find ' |
65 'and delete') | 94 'and delete') |
66 | 95 |
67 self.record_path = record_path | 96 self.record_path = record_path |
68 | 97 |
69 md5 = hashlib.md5() | 98 extended_info = [] |
99 outer_md5 = hashlib.md5() | |
70 for i in sorted(input_paths): | 100 for i in sorted(input_paths): |
71 _UpdateMd5ForPath(md5, i) | 101 inner_md5 = hashlib.md5() |
102 _UpdateMd5ForPath(inner_md5, i) | |
103 i = _TrimPathPrefix(i) | |
104 extended_info.append(i + '=' + inner_md5.hexdigest()) | |
105 # Include the digest in the overall diff, but not the path | |
106 outer_md5.update(inner_md5.hexdigest()) | |
107 | |
72 for s in input_strings: | 108 for s in input_strings: |
73 md5.update(s) | 109 outer_md5.update(s) |
74 self.new_digest = md5.hexdigest() | 110 extended_info.append(s) |
111 | |
112 self.new_digest = outer_md5.hexdigest() | |
113 self.new_extended_info = extended_info | |
75 | 114 |
76 self.old_digest = '' | 115 self.old_digest = '' |
116 self.old_extended_info = [] | |
77 if os.path.exists(self.record_path): | 117 if os.path.exists(self.record_path): |
78 with open(self.record_path, 'r') as old_record: | 118 with open(self.record_path, 'r') as old_record: |
79 self.old_digest = old_record.read() | 119 self.old_extended_info = [line.strip() for line in old_record] |
80 | 120 self.old_digest = self.old_extended_info.pop(0) |
81 def IsStale(self): | |
82 return self.old_digest != self.new_digest | |
83 | 121 |
84 def Write(self): | 122 def Write(self): |
85 with open(self.record_path, 'w') as new_record: | 123 with open(self.record_path, 'w') as new_record: |
86 new_record.write(self.new_digest) | 124 new_record.write(self.new_digest) |
125 new_record.write('\n' + '\n'.join(self.new_extended_info) + '\n') | |
126 | |
127 def DescribeDifference(self): | |
128 if self.old_digest == self.new_digest: | |
129 return "There's no difference" | |
jbudorick
2015/09/02 01:13:50
supernit: "... difference."
agrieve
2015/09/02 15:01:08
Done.
| |
130 if not self.old_digest: | |
131 return 'Previous stamp file not found.' | |
132 if not self.old_extended_info: | |
133 return 'Previous stamp file lacks extended info.' | |
134 diff = difflib.unified_diff(self.old_extended_info, self.new_extended_info) | |
135 return '\n'.join(diff) | |
OLD | NEW |