Chromium Code Reviews
|
| OLD | NEW |
|---|---|
| (Empty) | |
| 1 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 import re | |
| 6 | |
| 7 import model.app_config | |
| 8 import util | |
| 9 | |
| 10 | |
| 11 POLICY_TEMPLATES_FILE = 'chrome/app/policy/policy_templates.json' | |
| 12 ID_LINE_COMMENT = 'For your editing convenience: highest ID currently used:' | |
| 13 CONTEXT_THRESHOLD = 12 | |
|
iannucci
2013/08/15 03:48:05
Should we make a separate config file specifically
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
I'd expect that if we need changes, not only the c
| |
| 14 PROPERTY_NAME_RE = re.compile('\'(\\w+)\'\\s*:') | |
|
iannucci
2013/08/15 03:48:05
may be worth using a raw string to reduce the back
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
Good idea, done.
| |
| 15 MAX_LINE = 1000000 | |
| 16 | |
| 17 REVIEW_MESSAGE = ''' | |
| 18 Thanks for improving Chromium enterprise features! In general, | |
| 19 enterprise policies should work across all platforms and need to remain | |
| 20 backward compatible across several Chrome versions. To help avoid common | |
| 21 pitfalls, policy review bot has analyzed your change and added comments | |
| 22 with checklists of things to watch out for. | |
| 23 | |
| 24 Please direct comments and complaints about this service to | |
| 25 chrome-enterprise@google.com and/or mnissler@chromium.org. | |
| 26 ''' | |
| 27 | |
| 28 ADDITION_COMMENT = ''' | |
| 29 When adding a new policy setting, please consider the following: | |
| 30 | |
| 31 - Ideally, your new policy should be expressing the intention of an | |
| 32 administrator and/or cover a well-scoped user-visible behavior of the | |
| 33 browser as opposed to an implementation-specific parameter. Consider | |
| 34 that your implementation may change in the future, and you might have | |
| 35 to re-implement your policy on top of the new implementation. | |
|
iannucci
2013/08/15 03:48:05
I'm assuming these strings have all been vetted by
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
Yes, I've discussed them in a doc with the chrome/
| |
| 36 - Make sure your policy definition has proper supported_on declarations | |
| 37 specifying the platform and versions this policy is supported on. | |
| 38 - Make sure feature flags are correct. In particular: | |
| 39 - dynamic_refresh - whether your feature can adjust to changes in | |
| 40 policy at run time. You typically use PrefChangeRegistrar to do so. | |
| 41 - per_profile - whether your policy is browser-global or can be set | |
| 42 independently for each Chrome Profile. This is usually true if you | |
| 43 read the policy value from the Profile's PrefService and false if | |
| 44 you read it from the local_state PrefService. | |
| 45 - can_be_recommended - whether your feature supports admin-supplied | |
| 46 default values that the user can override. | |
| 47 - Make sure you put a helpful policy description: | |
| 48 - Describe the effect of setting the policy in a way that makes sense | |
| 49 to somebody who is not intimately familiar with your feature, such | |
| 50 as administrators and other Chromium developers. | |
| 51 - Do mention behavior for the cases where the policy gets ignored | |
| 52 (i.e. not configured, booleans only one value is effective). It's | |
| 53 nice for completeness and admins have been asking specifically for | |
| 54 this piece of information in the past. | |
| 55 - Write a browser_test for you new policy. The ideal test would fire up | |
| 56 the browser with the policy set and check whether the policy affects | |
| 57 user-visible behavior in the intended way. See | |
| 58 chrome/browser/policy/policy_browsertest.cc for examples. | |
| 59 - If you're adding a device policy for Chrome OS, be sure to update | |
| 60 chrome/browser/chromeos/policy/device_policy_decoder_chromeos.{h,cc} | |
| 61 so the policy shows up on the chrome://policy page. | |
|
iannucci
2013/08/15 03:48:05
It may make sense to actually turn these strings i
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
Good point. Done.
| |
| 62 ''' | |
| 63 | |
| 64 MODIFICATION_COMMENT = ''' | |
| 65 When modifying an existing policy setting, please consider the following: | |
| 66 | |
| 67 - Make sure the policy meta data is up-to-date, in particular | |
| 68 supported_on, and the feature flags. | |
| 69 - In general, don't change policy semantics in a way that is | |
| 70 incompatible (as determined by user/admin-visible behavior) with | |
| 71 previous semantics. In particular, consider that existing policy | |
| 72 deployments may affect both old and new browser versions, and both | |
| 73 should behave according to the admin's intentions. | |
| 74 - It is OK to expand semantics of policy values as long as the previous | |
| 75 policy description is compatible with the new behavior. | |
| 76 - It is OK to update feature implementations and the policy description | |
| 77 when Chrome changes as long as the intended effect of the policy | |
| 78 remains intact. | |
| 79 - The process for removing policies is to deprecate them first, wait a | |
| 80 few releases (if possible) and then remove them. Make sure you put the | |
| 81 deprecated flag if you deprecate a policy. | |
| 82 ''' | |
| 83 | |
| 84 | |
| 85 def nmin(*args): | |
| 86 """Calculates the minimum of |args|, ignoring None entries.""" | |
| 87 try: | |
| 88 return min(v for v in args if v is not None) | |
| 89 except ValueError: | |
|
iannucci
2013/08/15 03:48:05
This seems sketchy. Why ValueError?
Also, I think
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
min raises ValueError if the sequence is empty. I'
| |
| 90 return None | |
| 91 | |
| 92 | |
| 93 def nmax(*args): | |
| 94 """Calculates the maximum of |args|, ignoring None entries.""" | |
| 95 try: | |
| 96 return max(v for v in args if v is not None) | |
| 97 except ValueError: | |
| 98 return None | |
| 99 | |
| 100 | |
| 101 def nsub(a, b): | |
| 102 """Calculates a - b, returning None if either a or b is None""" | |
| 103 return None if (a is None or b is None) else a - b | |
| 104 | |
| 105 | |
| 106 def indent(line): | |
| 107 """Returns the indent level (number of leading spaces) for |line|.""" | |
| 108 nspaces = len(line) - len(line.lstrip(' ')) | |
| 109 return None if nspaces == 0 else nspaces | |
|
iannucci
2013/08/15 03:48:05
You may want to take a look at the textwrap module
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
This actually determines the indent from a given l
| |
| 110 | |
| 111 | |
| 112 class PolicyChangeParser(object): | |
|
iannucci
2013/08/15 03:48:05
Needs docs, code is hard to read without context/e
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
I've added documentation and some illustrative uni
| |
| 113 def __init__(self, lines): | |
| 114 self.lines = lines | |
| 115 self.chunks_list = [] | |
| 116 self.reset() | |
| 117 | |
| 118 def run(self): | |
| 119 self.chunks_list = [] | |
| 120 self.last_change = [None, None] | |
| 121 cursor = [None, None] | |
| 122 self.reset() | |
| 123 for (a_line, b_line, line) in self.lines: | |
| 124 # Skip comment lines. | |
| 125 if line.startswith('#'): | |
| 126 continue | |
| 127 | |
| 128 # See whether the current line has a JSON property. | |
| 129 keyword = None | |
| 130 match = PROPERTY_NAME_RE.search(line) | |
| 131 if match: | |
| 132 keyword = match.group(1).lower() | |
| 133 | |
| 134 | |
| 135 # Check whether the current block closes. | |
| 136 line_indent = indent(line) | |
| 137 if (self.block_indent is not None and | |
| 138 line_indent is not None and | |
| 139 line_indent < self.block_indent): | |
| 140 self.block_closed = True | |
| 141 | |
| 142 if (keyword == 'name' and self.block_closed): | |
| 143 # If we see the 'name' property, that likely indicates the start of a | |
| 144 # new policy. Start a new chunk. | |
| 145 self.flush_chunk() | |
| 146 | |
| 147 # Update various cursors. | |
| 148 cursor = [nmax(a_line, cursor[0]), nmax(b_line, cursor[1])] | |
| 149 offset = nmin(nsub(cursor[0], self.last_change[0]), | |
| 150 nsub(cursor[1], self.last_change[1])) | |
| 151 | |
| 152 if a_line > 0 and b_line == 0: | |
| 153 self.removals = True | |
| 154 self.last_change[0] = a_line | |
| 155 elif a_line == 0 and b_line > 0: | |
| 156 self.additions = True | |
| 157 self.last_change[1] = b_line | |
| 158 | |
| 159 if (offset is not None and | |
| 160 (offset > CONTEXT_THRESHOLD or | |
| 161 (offset > 1 and self.block_closed))): | |
| 162 # If the last chunk is too far away, assume a new one starts. | |
| 163 self.flush_chunk() | |
| 164 | |
| 165 # Try to figure out block indent from properties exclusively used for | |
| 166 # policy definitions. | |
| 167 if keyword in ('id', 'schema', 'future', 'items', 'features', | |
| 168 'supported_on', 'example_value', 'deprecated'): | |
| 169 self.block_indent = line_indent | |
| 170 | |
| 171 # Prefer the comment on the policy name property if we see it fly by. | |
| 172 if keyword == 'name': | |
| 173 # Attempt to filter out name labels on enum items. | |
| 174 if self.block_indent is not None and self.block_indent != line_indent: | |
| 175 pass | |
| 176 elif a_line > 0 and b_line == 0: | |
| 177 self.comment_pos[0] = a_line | |
| 178 elif a_line == 0 and b_line > 0: | |
| 179 self.comment_pos[1] = b_line | |
| 180 | |
| 181 self.chunk_start = [nmin(self.last_change[0], self.chunk_start[0]), | |
| 182 nmin(self.last_change[1], self.chunk_start[1])] | |
| 183 | |
| 184 # Flush the last chunk. | |
| 185 if self.chunk_start != [None, None]: | |
| 186 self.flush_chunk() | |
| 187 | |
| 188 @property | |
| 189 def chunks(self): | |
| 190 return self.chunks_list | |
| 191 | |
| 192 def flush_chunk(self): | |
| 193 self.comment_pos = [nmax(self.chunk_start[0], self.comment_pos[0]), | |
| 194 nmax(self.chunk_start[1], self.comment_pos[1])] | |
| 195 self.chunks_list.append( | |
| 196 util.ObjectDict( | |
| 197 { 'start': self.chunk_start, | |
| 198 'end': self.last_change, | |
| 199 'comment_pos': self.comment_pos, | |
| 200 'additions': self.additions, | |
| 201 'removals': self.removals })) | |
| 202 self.reset() | |
| 203 | |
| 204 def reset(self): | |
| 205 # This is called from __init__. | |
| 206 # pylint: disable=W0201 | |
| 207 self.chunk_start = [None, None] | |
| 208 self.last_change = [None, None] | |
| 209 self.comment_pos = [None, None] | |
| 210 self.block_indent = None | |
| 211 self.block_closed = False | |
| 212 self.additions = False | |
| 213 self.removals = False | |
| 214 | |
| 215 | |
| 216 def process(addr, message, review, rietveld): | |
| 217 """Handles reviews for chrome/app/policy/policy_templates.json. | |
| 218 | |
| 219 This looks at the patch to identify additions/modifications to policy | |
| 220 definitions and posts comments with a checklist intended for the author and | |
| 221 reviewer to go through in order to catch common mistakes. | |
| 222 """ | |
| 223 | |
| 224 if POLICY_TEMPLATES_FILE not in review.latest_patchset.files: | |
| 225 return | |
| 226 | |
| 227 # Only process the change if the mail is directly to us or we haven't | |
| 228 # processed this review yet. | |
| 229 client_id = model.app_config.get().client_id | |
| 230 if (not addr in util.get_emails(getattr(message, 'to', '')) and | |
| 231 client_id in [m.sender for m in review.issue_data.messages]): | |
| 232 return | |
|
iannucci
2013/08/15 03:48:05
So the idea here is that you could add reviewbot a
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
Correct. Once we've done so a couple times and con
| |
| 233 | |
| 234 # Don't process reverts. | |
| 235 if 'revert' in review.issue_data.description.lower(): | |
| 236 return | |
| 237 | |
| 238 # Parse the patch, look at the chunks and generate inline comments. | |
| 239 parser = PolicyChangeParser( | |
|
iannucci
2013/08/15 03:48:05
why not just make PolicyChangeParser a function re
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
It's got all this state to carry around, which is
| |
| 240 review.latest_patchset.files[POLICY_TEMPLATES_FILE].patch.lines) | |
| 241 parser.run() | |
| 242 for chunk in parser.chunks: | |
| 243 if chunk.additions and not chunk.removals: | |
| 244 message = ADDITION_COMMENT | |
| 245 else: | |
| 246 message = MODIFICATION_COMMENT | |
| 247 | |
| 248 if chunk.comment_pos[1] is not None: | |
| 249 line, side = chunk.comment_pos[1], 'b' | |
| 250 elif chunk.comment_pos[0] is not None: | |
| 251 line, side = chunk.comment_pos[0], 'a' | |
| 252 else: | |
| 253 # No suitable position? | |
| 254 continue | |
| 255 | |
| 256 rietveld.add_inline_comment( | |
| 257 review.issue_id, review.latest_patchset.patchset, | |
| 258 review.latest_patchset.files[POLICY_TEMPLATES_FILE].id, | |
| 259 line, side, message) | |
|
iannucci
2013/08/15 03:48:05
Maybe I'm unfamiliar with the types of changes her
Mattias Nissler (ping if slow)
2013/08/16 12:49:22
I've tried it on GIT history and it usually posted
| |
| 260 | |
| 261 # Finally, post all inline comments. | |
| 262 if len(parser.chunks) > 0: | |
| 263 rietveld.post_comment(review.issue_id, REVIEW_MESSAGE, True) | |
| OLD | NEW |