Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 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 """Updates enums in histograms.xml file with values read from provided C++ enum. | 5 """Updates enums in histograms.xml file with values read from provided C++ enum. |
| 6 | 6 |
| 7 If the file was pretty-printed, the updated version is pretty-printed too. | 7 If the file was pretty-printed, the updated version is pretty-printed too. |
| 8 """ | 8 """ |
| 9 | 9 |
| 10 import logging | 10 import logging |
| 11 import os | 11 import os |
| 12 import re | 12 import re |
| 13 import sys | 13 import sys |
| 14 | 14 |
| 15 from xml.dom import minidom | 15 from xml.dom import minidom |
| 16 | 16 |
| 17 sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common')) | 17 sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common')) |
| 18 import diff_util | 18 import diff_util |
| 19 import path_util | 19 import path_util |
| 20 | 20 |
| 21 import print_style | 21 import print_style |
| 22 | 22 |
| 23 | 23 |
| 24 HISTOGRAMS_PATH = path_util.GetHistogramsFile() | 24 HISTOGRAMS_PATH = path_util.GetHistogramsFile() |
| 25 | 25 |
| 26 | 26 |
| 27 class UserError(Exception): | 27 class UserError(Exception): |
| 28 def __init__(self, message): | 28 def __init__(self, message): |
|
Ilya Sherman
2017/04/26 00:18:50
Hmm, why did you add extra indentation to all of t
lunalu1
2017/04/27 18:31:42
My bad, I didn't realize chromium and Blink had di
| |
| 29 Exception.__init__(self, message) | 29 Exception.__init__(self, message) |
| 30 | 30 |
| 31 @property | 31 @property |
| 32 def message(self): | 32 def message(self): |
| 33 return self.args[0] | 33 return self.args[0] |
| 34 | |
|
Ilya Sherman
2017/04/26 00:18:50
nit: Please leave two blank lines between top-leve
| |
| 35 class DuplicatedValue(Exception): | |
| 36 """Exception raised for duplicated enum values. | |
| 37 | |
| 38 Attributes: | |
| 39 first_label: First enum label that shares the duplicated enum value. | |
| 40 second_label: Second enum label that shares the duplicated enum value. | |
| 41 """ | |
| 42 def __init__(self, first_label, second_label): | |
| 43 self.first_label = first_label | |
| 44 self.second_label = second_label | |
| 34 | 45 |
| 35 | 46 |
| 36 def Log(message): | 47 def Log(message): |
| 37 logging.info(message) | 48 logging.info(message) |
| 38 | 49 |
| 39 | 50 |
| 40 def ReadHistogramValues(filename, start_marker, end_marker, strip_k_prefix): | 51 def ReadHistogramValues(filename, start_marker, end_marker, strip_k_prefix): |
| 41 """Returns a dictionary of enum values and a pair of labels that have the same | 52 """Creates a dictionary of enum values, read from a C++ file. |
| 42 enum values, read from a C++ file. | 53 |
| 43 | 54 Args: |
| 44 Args: | 55 filename: The unix-style path (relative to src/) of the file to open. |
| 45 filename: The unix-style path (relative to src/) of the file to open. | 56 start_marker: A regex that signifies the start of the enum values. |
| 46 start_marker: A regex that signifies the start of the enum values. | 57 end_marker: A regex that signifies the end of the enum values. |
| 47 end_marker: A regex that signifies the end of the enum values. | 58 strip_k_prefix: Set to True if enum values are declared as kFoo and the |
| 48 strip_k_prefix: Set to True if enum values are declared as kFoo and the | 59 'k' should be stripped. |
| 49 'k' should be stripped. | 60 |
| 61 Returns: | |
| 62 A dict mapping enum labels to the corresponding enum values. | |
| 63 | |
| 64 Raises: | |
| 65 DuplicatedValue: An error when two enum labels share the same value. | |
| 50 """ | 66 """ |
| 51 # Read the file as a list of lines | 67 # Read the file as a list of lines |
| 52 with open(path_util.GetInputFile(filename)) as f: | 68 with open(path_util.GetInputFile(filename)) as f: |
| 53 content = f.readlines() | 69 content = f.readlines() |
| 54 | 70 |
| 55 START_REGEX = re.compile(start_marker) | 71 START_REGEX = re.compile(start_marker) |
| 56 ITEM_REGEX = re.compile(r'^(\w+)') | 72 ITEM_REGEX = re.compile(r'^(\w+)') |
| 57 ITEM_REGEX_WITH_INIT = re.compile(r'(\w+)\s*=\s*(\d*)') | 73 ITEM_REGEX_WITH_INIT = re.compile(r'(\w+)\s*=\s*(\d*)') |
| 58 WRAPPED_INIT = re.compile(r'(\d+)') | 74 WRAPPED_INIT = re.compile(r'(\d+)') |
| 59 END_REGEX = re.compile(end_marker) | 75 END_REGEX = re.compile(end_marker) |
| 60 | 76 |
| 61 iterator = iter(content) | 77 iterator = iter(content) |
| 62 # Find the start of the enum | 78 # Find the start of the enum |
| 63 for line in iterator: | 79 for line in iterator: |
| 64 if START_REGEX.match(line.strip()): | 80 if START_REGEX.match(line.strip()): |
| 65 break | 81 break |
| 66 | 82 |
| 67 enum_value = 0 | 83 enum_value = 0 |
| 68 result = {} | 84 result = {} |
| 69 for line in iterator: | 85 for line in iterator: |
| 70 line = line.strip() | 86 line = line.strip() |
| 71 # Exit condition: we reached last enum value | 87 # Exit condition: we reached last enum value |
| 72 if END_REGEX.match(line): | 88 if END_REGEX.match(line): |
| 73 break | 89 break |
| 74 # Inside enum: generate new xml entry | 90 # Inside enum: generate new xml entry |
| 75 m = ITEM_REGEX_WITH_INIT.match(line) | 91 m = ITEM_REGEX_WITH_INIT.match(line) |
| 76 if m: | 92 if m: |
| 77 label = m.group(1) | 93 label = m.group(1) |
| 78 if m.group(2): | 94 if m.group(2): |
| 79 enum_value = int(m.group(2)) | 95 enum_value = int(m.group(2)) |
| 80 else: | 96 else: |
| 81 # Enum name is so long that the value wrapped to the next line | 97 # Enum name is so long that the value wrapped to the next line |
| 82 next_line = next(iterator).strip() | 98 next_line = next(iterator).strip() |
| 83 enum_value = int(WRAPPED_INIT.match(next_line).group(1)) | 99 enum_value = int(WRAPPED_INIT.match(next_line).group(1)) |
| 84 else: | 100 else: |
| 85 m = ITEM_REGEX.match(line) | 101 m = ITEM_REGEX.match(line) |
| 86 if m: | 102 if m: |
| 87 label = m.group(1) | 103 label = m.group(1) |
| 88 else: | 104 else: |
| 89 continue | 105 continue |
| 90 # If two enum labels have the same value | 106 # If two enum labels have the same value |
| 91 if enum_value in result: | 107 if enum_value in result: |
| 92 return result, (result[enum_value], label) | 108 raise DuplicatedValue(result[enum_value], label) |
| 93 if strip_k_prefix: | 109 if strip_k_prefix: |
| 94 assert label.startswith('k'), "Enum " + label + " should start with 'k'." | 110 assert label.startswith('k'), ("Enum " + label + |
| 95 label = label[1:] | 111 " should start with 'k'.") |
| 96 result[enum_value] = label | 112 label = label[1:] |
| 97 enum_value += 1 | 113 result[enum_value] = label |
| 98 return result, None | 114 enum_value += 1 |
| 115 return result | |
| 99 | 116 |
| 100 | 117 |
| 101 def CreateEnumItemNode(document, value, label): | 118 def CreateEnumItemNode(document, value, label): |
| 102 """Creates an int element to append to an enum.""" | 119 """Creates an int element to append to an enum.""" |
| 103 item_node = document.createElement('int') | 120 item_node = document.createElement('int') |
| 104 item_node.attributes['value'] = str(value) | 121 item_node.attributes['value'] = str(value) |
| 105 item_node.attributes['label'] = label | 122 item_node.attributes['label'] = label |
| 106 return item_node | 123 return item_node |
| 107 | 124 |
| 108 | 125 |
| 109 def UpdateHistogramDefinitions(histogram_enum_name, source_enum_values, | 126 def UpdateHistogramDefinitions(histogram_enum_name, source_enum_values, |
| 110 source_enum_path, document): | 127 source_enum_path, document): |
| 111 """Updates the enum node named |histogram_enum_name| based on the definition | 128 """Updates the enum node named |histogram_enum_name| based on the definition |
| 112 stored in |source_enum_values|. Existing items for which |source_enum_values| | 129 stored in |source_enum_values|. Existing items for which |
| 113 doesn't contain any corresponding data will be preserved. |source_enum_path| | 130 |source_enum_values| doesn't contain any corresponding data will be |
| 114 will be used to insert a comment. | 131 preserved. |source_enum_path| will be used to insert a comment. |
| 115 """ | 132 """ |
| 116 # Get a dom of <enum name=|histogram_enum_name| ...> node in |document|. | 133 # Get a dom of <enum name=|histogram_enum_name| ...> node in |document|. |
| 117 for enum_node in document.getElementsByTagName('enum'): | 134 for enum_node in document.getElementsByTagName('enum'): |
| 118 if enum_node.attributes['name'].value == histogram_enum_name: | 135 if enum_node.attributes['name'].value == histogram_enum_name: |
| 119 break | 136 break |
| 120 else: | 137 else: |
| 121 raise UserError('No {0} enum node found'.format(histogram_enum_name)) | 138 raise UserError('No {0} enum node found'.format(histogram_enum_name)) |
| 122 | 139 |
| 123 new_item_nodes = {} | 140 new_item_nodes = {} |
| 124 new_comments = [] | 141 new_comments = [] |
| 125 | 142 |
| 126 # Add a "Generated from (...)" comment. | 143 # Add a "Generated from (...)" comment. |
| 127 new_comments.append( | 144 new_comments.append( |
| 128 document.createComment(' Generated from {0} '.format(source_enum_path))) | 145 document.createComment(' Generated from {0} '.format(source_enum_path))) |
| 129 | 146 |
| 130 # Create item nodes for each of the enum values. | 147 # Create item nodes for each of the enum values. |
| 131 for value, label in source_enum_values.iteritems(): | 148 for value, label in source_enum_values.iteritems(): |
| 132 new_item_nodes[value] = CreateEnumItemNode(document, value, label) | 149 new_item_nodes[value] = CreateEnumItemNode(document, value, label) |
| 133 | 150 |
| 134 # Scan existing nodes in |enum_node| for old values and preserve them. | 151 # Scan existing nodes in |enum_node| for old values and preserve them. |
| 135 # - Preserve comments other than the 'Generated from' comment. NOTE: | 152 # - Preserve comments other than the 'Generated from' comment. NOTE: |
| 136 # this does not preserve the order of the comments in relation to the | 153 # this does not preserve the order of the comments in relation to the |
| 137 # old values. | 154 # old values. |
| 138 # - Drop anything else. | 155 # - Drop anything else. |
| 139 SOURCE_COMMENT_REGEX = re.compile('^ Generated from ') | 156 SOURCE_COMMENT_REGEX = re.compile('^ Generated from ') |
| 140 for child in enum_node.childNodes: | 157 for child in enum_node.childNodes: |
| 141 if child.nodeName == 'int': | 158 if child.nodeName == 'int': |
| 142 value = int(child.attributes['value'].value) | 159 value = int(child.attributes['value'].value) |
| 143 if not source_enum_values.has_key(value): | 160 if not source_enum_values.has_key(value): |
| 144 new_item_nodes[value] = child | 161 new_item_nodes[value] = child |
| 145 # Preserve existing non-generated comments. | 162 # Preserve existing non-generated comments. |
| 146 elif (child.nodeType == minidom.Node.COMMENT_NODE and | 163 elif (child.nodeType == minidom.Node.COMMENT_NODE and |
| 147 SOURCE_COMMENT_REGEX.match(child.data) is None): | 164 SOURCE_COMMENT_REGEX.match(child.data) is None): |
| 148 new_comments.append(child) | 165 new_comments.append(child) |
| 149 | 166 |
| 150 # Update |enum_node|. First, remove everything existing. | 167 # Update |enum_node|. First, remove everything existing. |
| 151 while enum_node.hasChildNodes(): | 168 while enum_node.hasChildNodes(): |
| 152 enum_node.removeChild(enum_node.lastChild) | 169 enum_node.removeChild(enum_node.lastChild) |
| 153 | 170 |
| 154 # Add comments at the top. | 171 # Add comments at the top. |
| 155 for comment in new_comments: | 172 for comment in new_comments: |
| 156 enum_node.appendChild(comment) | 173 enum_node.appendChild(comment) |
| 157 | 174 |
| 158 # Add in the new enums. | 175 # Add in the new enums. |
| 159 for value in sorted(new_item_nodes.iterkeys()): | 176 for value in sorted(new_item_nodes.iterkeys()): |
| 160 enum_node.appendChild(new_item_nodes[value]) | 177 enum_node.appendChild(new_item_nodes[value]) |
| 161 | 178 |
| 162 | 179 |
| 163 def _GetOldAndUpdatedXml(histogram_enum_name, source_enum_values, | 180 def _GetOldAndUpdatedXml(histogram_enum_name, source_enum_values, |
| 164 source_enum_path): | 181 source_enum_path): |
| 165 """Reads old histogram from |histogram_enum_name| from |HISTOGRAMS_PATH|, and | 182 """Reads old histogram from |histogram_enum_name| from |HISTOGRAMS_PATH|, |
| 166 calculates new histogram from |source_enum_values| from |source_enum_path|, | 183 and calculates new histogram from |source_enum_values| from |
| 167 and returns both in XML format. | 184 |source_enum_path|, and returns both in XML format. |
| 168 """ | 185 """ |
| 169 Log('Reading existing histograms from "{0}".'.format(HISTOGRAMS_PATH)) | 186 Log('Reading existing histograms from "{0}".'.format(HISTOGRAMS_PATH)) |
| 170 with open(HISTOGRAMS_PATH, 'rb') as f: | 187 with open(HISTOGRAMS_PATH, 'rb') as f: |
| 171 histograms_doc = minidom.parse(f) | 188 histograms_doc = minidom.parse(f) |
| 172 f.seek(0) | 189 f.seek(0) |
| 173 xml = f.read() | 190 xml = f.read() |
| 174 | 191 |
| 175 Log('Comparing histograms enum with new enum definition.') | 192 Log('Comparing histograms enum with new enum definition.') |
| 176 UpdateHistogramDefinitions(histogram_enum_name, source_enum_values, | 193 UpdateHistogramDefinitions(histogram_enum_name, source_enum_values, |
| 177 source_enum_path, histograms_doc) | 194 source_enum_path, histograms_doc) |
| 178 | 195 |
| 179 new_xml = print_style.GetPrintStyle().PrettyPrintNode(histograms_doc) | 196 new_xml = print_style.GetPrintStyle().PrettyPrintNode(histograms_doc) |
| 180 return (xml, new_xml) | 197 return (xml, new_xml) |
| 181 | 198 |
| 182 | 199 |
| 183 def HistogramNeedsUpdate(histogram_enum_name, source_enum_path, start_marker, | 200 def HistogramNeedsUpdate(histogram_enum_name, source_enum_path, start_marker, |
| 184 end_marker, strip_k_prefix = False): | 201 end_marker, strip_k_prefix = False): |
| 185 """Reads a C++ enum from a .h file and does a dry run of updating | 202 """Reads a C++ enum from a .h file and does a dry run of updating |
| 186 histograms.xml to match. Returns true if the histograms.xml file would be | 203 histograms.xml to match. |
| 187 changed. | 204 |
| 188 | 205 Args: |
| 189 Args: | 206 histogram_enum_name: The name of the XML <enum> attribute to update. |
| 190 histogram_enum_name: The name of the XML <enum> attribute to update. | 207 source_enum_path: A unix-style path, relative to src/, giving |
| 191 source_enum_path: A unix-style path, relative to src/, giving | 208 the C++ header file from which to read the enum. |
| 192 the C++ header file from which to read the enum. | 209 start_marker: A regular expression that matches the start of the C++ |
| 193 start_marker: A regular expression that matches the start of the C++ enum. | 210 enum. |
| 194 end_marker: A regular expression that matches the end of the C++ enum. | 211 end_marker: A regular expression that matches the end of the C++ enum. |
| 195 strip_k_prefix: Set to True if enum values are declared as kFoo and the | 212 strip_k_prefix: Set to True if enum values are declared as kFoo and the |
| 196 'k' should be stripped. | 213 'k' should be stripped. |
| 197 """ | 214 |
| 198 Log('Reading histogram enum definition from "{0}".'.format(source_enum_path)) | 215 Returns: |
| 199 source_enum_values, duplicated_values = ReadHistogramValues( | 216 A boolean indicating whether the histograms.xml file would be changed. |
| 200 source_enum_path, start_marker, end_marker, strip_k_prefix) | 217 |
| 201 if duplicated_values: | 218 Raises: |
| 202 return False, duplicated_values | 219 DuplicatedValue: An error when two enum labels share the same value. |
| 203 | 220 """ |
| 204 (xml, new_xml) = _GetOldAndUpdatedXml(histogram_enum_name, source_enum_values, | 221 Log('Reading histogram enum definition from "{0}".'.format( |
| 205 source_enum_path) | 222 source_enum_path)) |
| 206 return xml != new_xml, None | 223 try: |
| 224 source_enum_values, duplicated_values = ReadHistogramValues( | |
| 225 source_enum_path, start_marker, end_marker, strip_k_prefix) | |
| 226 except DuplicatedValue: | |
| 227 raise | |
|
Ilya Sherman
2017/04/26 00:18:50
There's no need to use a try/except here. The nat
| |
| 228 | |
| 229 (xml, new_xml) = _GetOldAndUpdatedXml(histogram_enum_name, | |
| 230 source_enum_values, | |
| 231 source_enum_path) | |
| 232 return xml != new_xml | |
| 207 | 233 |
| 208 | 234 |
| 209 def UpdateHistogramFromDict(histogram_enum_name, source_enum_values, | 235 def UpdateHistogramFromDict(histogram_enum_name, source_enum_values, |
| 210 source_enum_path): | 236 source_enum_path): |
| 211 """Updates |histogram_enum_name| enum in histograms.xml file with values | 237 """Updates |histogram_enum_name| enum in histograms.xml file with values |
| 212 from the {value: 'key'} dictionary |source_enum_values|. A comment is added | 238 from the {value: 'key'} dictionary |source_enum_values|. A comment is added |
| 213 to histograms.xml citing that the values in |histogram_enum_name| were | 239 to histograms.xml citing that the values in |histogram_enum_name| were |
| 214 sourced from |source_enum_path|. | 240 sourced from |source_enum_path|. |
| 215 """ | 241 """ |
| 216 (xml, new_xml) = _GetOldAndUpdatedXml(histogram_enum_name, source_enum_values, | 242 (xml, new_xml) = _GetOldAndUpdatedXml(histogram_enum_name, |
| 217 source_enum_path) | 243 source_enum_values, |
| 218 if not diff_util.PromptUserToAcceptDiff( | 244 source_enum_path) |
| 219 xml, new_xml, 'Is the updated version acceptable?'): | 245 if not diff_util.PromptUserToAcceptDiff( |
| 220 Log('Cancelled.') | 246 xml, new_xml, 'Is the updated version acceptable?'): |
| 221 return | 247 Log('Cancelled.') |
| 222 | 248 return |
| 223 with open(HISTOGRAMS_PATH, 'wb') as f: | 249 |
| 224 f.write(new_xml) | 250 with open(HISTOGRAMS_PATH, 'wb') as f: |
| 225 | 251 f.write(new_xml) |
| 226 Log('Done.') | 252 |
| 253 Log('Done.') | |
| 227 | 254 |
| 228 | 255 |
| 229 def UpdateHistogramEnum(histogram_enum_name, source_enum_path, | 256 def UpdateHistogramEnum(histogram_enum_name, source_enum_path, |
| 230 start_marker, end_marker, strip_k_prefix = False): | 257 start_marker, end_marker, strip_k_prefix = False): |
| 231 """Reads a C++ enum from a .h file and updates histograms.xml to match. | 258 """Reads a C++ enum from a .h file and updates histograms.xml to match. |
| 232 | 259 |
| 233 Args: | 260 Args: |
| 234 histogram_enum_name: The name of the XML <enum> attribute to update. | 261 histogram_enum_name: The name of the XML <enum> attribute to update. |
| 235 source_enum_path: A unix-style path, relative to src/, giving | 262 source_enum_path: A unix-style path, relative to src/, giving |
| 236 the C++ header file from which to read the enum. | 263 the C++ header file from which to read the enum. |
| 237 start_marker: A regular expression that matches the start of the C++ enum. | 264 start_marker: A regular expression that matches the start of the C++ |
| 238 end_marker: A regular expression that matches the end of the C++ enum. | 265 enum. |
| 239 strip_k_prefix: Set to True if enum values are declared as kFoo and the | 266 end_marker: A regular expression that matches the end of the C++ enum. |
| 240 'k' should be stripped. | 267 strip_k_prefix: Set to True if enum values are declared as kFoo and the |
| 241 """ | 268 'k' should be stripped. |
| 242 | 269 """ |
| 243 Log('Reading histogram enum definition from "{0}".'.format(source_enum_path)) | 270 |
| 244 source_enum_values, ignored = ReadHistogramValues(source_enum_path, | 271 Log('Reading histogram enum definition from "{0}".'.format( |
| 245 start_marker, end_marker, strip_k_prefix) | 272 source_enum_path)) |
| 246 | 273 source_enum_values, ignored = ReadHistogramValues(source_enum_path, |
| 247 UpdateHistogramFromDict(histogram_enum_name, source_enum_values, | 274 start_marker, |
| 248 source_enum_path) | 275 end_marker, |
| 276 strip_k_prefix) | |
| 277 | |
| 278 UpdateHistogramFromDict(Histogram_enum_name, source_enum_values, | |
|
Ilya Sherman
2017/04/26 00:18:50
nit: s/Histogram/histogram (text editor keyboard s
| |
| 279 source_enum_path) | |
| OLD | NEW |